Невозможно назначить строковый литерал вектору std::string в штучной упаковке

Это упрощенная версия моей системы типов:

#include <string>
#include <vector>

template<typename T>
class Box {
public:
    Box(const T& value) : _value(value) {};
private:
    T _value;
    /* ... */
};

typedef Box<int> Int;
typedef Box<double> Double;
typedef Box<std::string> String;

int main(int argc, char* argv[]) {
    String a("abc");
    std::vector<String> b = { std::string("abc"), std::string("def") };

    // error C2664: 'Box<std::string>::Box(const Box<std::string> &)' : cannot convert argument 1 from 'const char' to 'const std::string &'
    std::vector<String> c = { "abc", "def" };
}

В то время как a и b компилируются, c нет, и причина, по-видимому, в том, что я пытаюсь инициализировать из const char. Это вызывает два вопроса:

  1. Почему b можно, а c нельзя? Это из-за вложенного шаблона в std::vector<Box<std::string> >?

  2. Могу ли я заставить работать c, не разрушая общий механизм бокса (ср. typedef Box<double> Double?


person PhilLab    schedule 22.05.2017    source источник
comment
clang и gcc дают мне эту ошибку с вашим примером: main.cpp:18:12: error: no viable conversion from 'const char [4]' to 'String' (aka 'Box<basic_string<char> >') String a = "abc"; (сообщения об ошибках меняются между компиляторами, но ошибка одна и та же). Может быть, ошибка в том, что вы не можете использовать неявное преобразование (путем вызова конструктора или с помощью определяемого пользователем оператора приведения) с шаблоном?   -  person nefas    schedule 22.05.2017
comment
Упс, ты прав. Кажется, я сделал ошибку, проверяя, что компилируется, а что нет. Итак, половина тайны раскрыта. Адаптировал вопрос   -  person PhilLab    schedule 22.05.2017


Ответы (3)


c в настоящее время требуется 2 неявных пользовательских преобразования (const char [N] -> std::string -> String), тогда как разрешено только одно.

Вы можете добавить конструктор шаблонов в Box

template<typename T>
class Box {
public:
    Box() = default;
    Box(const Box&) = default;
    Box(Box&&) default;
    ~Box() = default;

    Box& operator=(const Box&) = default;
    Box& operator=(Box&&) = default;

    template <typename U0, typename ...Us,
              std::enable_if_t<std::is_constructible<T, U0, Us...>::value
                               && (!std::is_same<Box, std::decay_t<U0>>::value
                                  || sizeof...(Us) != 0)>* = nullptr>
    Box(U0&& u0, Us&&... us) : _value(std::forward<U0>(u0), std::forward<Us>(us)...) {}
private:
    T _value;
    /* ... */
};

Демо Демонстрация 2

person Jarod42    schedule 22.05.2017
comment
Я думал 3 часа, опытные люди лучше :) - person snr; 22.05.2017
comment
Есть ли причина ограничить это одним аргументом? Например. почему бы не принять пару итераторов для Box<std::string> ? - person MSalters; 22.05.2017
comment
Эта вещь нуждается в ограничениях. Много их. - person T.C.; 22.05.2017
comment
Не рекомендуется использовать переадресацию ссылок в перегруженной функции, такой как конструктор класса. Пересылка пересылки будет затронута первой во время разрешения перегрузки во многих случаях, когда вы этого не хотите. Например, Box<int> b1(1); Box<int> b2(b1) обращается к конструктору пересылки, а не к конструктору копирования, созданному компилятором. - person 0x5453; 22.05.2017
comment
@ 0x5453: версия улучшена - person Jarod42; 22.05.2017
comment
Boxed(U0&& u0, Us&&... us), отключить на sizeof...(Us==0)&&std::is_same<std::decay_t<U0>,Boxed>{} -- добавить Boxed()=default;. Теперь, даже если T можно построить из Boxed, вы не замените операции копирования/перемещения конструкцией T. - person Yakk - Adam Nevraumont; 23.05.2017
comment
Это не компилируется для меня. Я исправил демонстрацию, чтобы использовать код в этом ответе, а также исправил очевидную опечатку (от Box(T&&) default до Box(T&&) = default), но я по-прежнему вызывает ошибки only special member functions may be defaulted, copy constructor is implicitly deleted и invokes deleted constructor. - person joshtch; 02.07.2018
comment
@joshtch: Опечатки тоже исправлены: добавлена ​​Demo2. - person Jarod42; 02.07.2018

Глядя на ваш исходный код только в основной функциональной части:

int main(int argc, char* argv[]) {
    String a("abc");
    std::vector<String> b = { std::string("abc"), std::string("def") };

    // error C2664: 'Box<std::string>::Box(const Box<std::string> &)' :
    // cannot convert argument 1 from 'const char' to 'const std::string &'
    std::vector<String> c = { "abc", "def" };
}

Ваша первая строка кода:

String a("abc");

Использует typedef версию Box<std::string>, которую этот шаблон класса принимает const T&, и поскольку эта версия шаблона ожидает std::string, он использует конструктор std::string's для создания std::string из const char[3], и это нормально.

Ваша следующая строка кода:

std::vector<String> b = { std::string("abc"), std::string("def") };

Является std::vector<T> того же выше. Так что это также работает, поскольку вы инициализируете vector<T> допустимыми объектами std::string.

В вашей последней строке кода:

std::vector<String> c = { "abc", "def" };

Здесь вы объявляете c как vector<T>, где T является typedef версией Box<std::string>, однако вы не инициализируете std::vector<T> типами Box<std::string>. Вы пытаетесь инициализировать его с помощью const char[3] объектов или строковых литералов.

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

std::vector<String> c = { String("abc"), String("def") };

ИЗМЕНИТЬ -- я хотел использовать конструктор для String, а не std::string внес соответствующие изменения.

person Francis Cugler    schedule 22.05.2017

Вы можете использовать вспомогательную функцию, которая создает соответствующий тип:

template <typename T,typename R>
Box<T> make_boxed(const R& value){
    return Box<T>(value);
}

может показаться дополнительным усложнением необходимость указывать T, с другой стороны, вы можете использовать auto для возвращаемого типа. Полный пример:

#include <string>

template<typename T>
class Box {
public:
    Box(const T& value) : _value(value) {};
private:
    T _value;
    /* ... */
};

typedef Box<std::string> String;

int main(int argc, char* argv[]) {
    auto a = make_boxed<std::string>("asd");
}
person 463035818_is_not_a_number    schedule 22.05.2017