Не удается преобразовать тип при инициализации

Я думаю, что я что-то пропустил, и я не знаю, что именно. Давайте посмотрим на фрагмент кода.

template <typename T>
struct Foo { 
    Foo (int n, int p, string s, T t = {})
    : m_n {n}, m_p {p}, m_s {s}, m_t {t}
    {}

    const int m_n;
    const int m_p;
    const string m_s;
    T m_t;
};

И использование выглядит так:

Foo<int> f_int1 {1, 2, "A", 155};
Foo<int> f_int2 {1, 2, "A"};

Все как задумано. Но когда я хочу иметь определяемый пользователем тип в качестве T-параметра Foo, возникают некоторые ошибки. Рассмотреть возможность:

struct Boo {
    int z;
    int l;
};

И использование:

Foo<Boo> f_boo1 {1, 2, "A"};
Foo<Boo> f_boo2 {1, 2, "A", {1, 2}};

Обе эти инструкции дают (gcc 4.8.1):

cannot convert ‘Boo’ to ‘int’ in initialization

Я могу создавать объекты Boo следующим образом:

Boo boo1 {};
Boo boo2 {1, 2};

Итак, не могли бы вы сказать мне, где проблема?

Возможное решение:

struct Boo {
    Boo () : z {}, l {} {}
    Boo (int p1, int p2) : z {p1}, l {p2} {}

    int z;
    int l;
};

И обе приведенные ниже инструкции работают по назначению:

Foo<Boo> f_boo1 {1, 2, "A"};
Foo<Boo> f_boo2 {1, 2, "A", {1, 2}};

Для меня это нормально, я не вижу причин, почему бы не добавить два конструктора в класс, но что делать, если тип не мой? Должен ли я написать простую оболочку с конструкторами?

Спасибо, Артур


person Artur Pyszczuk    schedule 24.01.2015    source источник
comment
связанная та же причина ошибки   -  person Ryan Haining    schedule 24.01.2015


Ответы (1)


Это потому, что вы пытаетесь выполнить агрегатную инициализацию на Boo. См. §8.5.4/3:

Список-инициализация объекта или ссылки типа T определяется следующим образом:

— Если T является агрегатом, выполняется инициализация агрегата (8.5.1).

Вы собираетесь копировать-конструировать свой Boo... но на самом деле вы выполняете агрегатную инициализацию, что приводит к попытке построить int z из Boo, отсюда и ошибка

ошибка: невозможно преобразовать "Boo" в "int"

Обратите внимание, что вы можете воспроизвести свою проблему в гораздо меньшем количестве кода и без каких-либо шаблонов:

Boo b;
Boo b2{b}; // error

Исправление простое. Просто не используйте инициализацию списка:

template <typename T>
struct Foo { 
    Foo (int n, int p, string s, T t = {})
    : m_n {n}, m_p {p}, m_s {s}, m_t(t)
    //                           ^^^^^^
{};
person Barry    schedule 24.01.2015
comment
не могли бы вы немного пояснить, почему {1,2} неявно преобразуется в Boo вместо того, чтобы передаваться в ctor с помощью агрегатной инициализации? Разве Foo<T> не является агрегатом? - person vsoftco; 24.01.2015
comment
@vsoftco Boo{1,2} выполняет инициализацию агрегатов. Boo{anything, at, all} будет агрегатной инициализацией, потому что Boo является агрегатом. - person Barry; 24.01.2015
comment
@vsoftco Foo<T> не является агрегатом, поскольку у него есть конструктор, предоставляемый пользователем. Полное определение (8.5.1): Агрегат — это массив или класс без конструкторов, предоставляемых пользователем, без частных или защищенных нестатических элементов данных, без базовых классов и без виртуальных функций. - person Barry; 24.01.2015
comment
Это дефект стандарта, который был исправлен решением Выпуск CWG 1467. - person T.C.; 25.01.2015
comment
@Т.С. Хорошо! Знаете ли вы, как отслеживать, когда компиляторы обновляются для таких вещей? clang 3.5.0 и gcc 4.9.2 с -std=c++14 все еще ошибаются (что раньше было правильно?) для этого. - person Barry; 25.01.2015
comment
@Barry, кажется, исправлен на стволе gcc, но не с лязгом. Однако я не знаю никакого хорошего способа отслеживать, когда компиляторы реализуют решения основных проблем. - person T.C.; 25.01.2015