无法将字符串字面值赋值给包装的std::string向量

13
这是我的类型系统的简化版:
#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" };
}

虽然ab可以编译,但c不能编译,原因似乎是我试图从const char初始化。这引发了两个问题:

  1. 为什么b可以而c不行?是因为std::vector<Box<std::string> >中的嵌套模板吗?

  2. 我能否使c正常工作而不破坏通用装箱机制(参见typedef Box<double> Double)?


clang和gcc在使用您的示例时给我返回了以下错误: ``main.cpp:18:12: error: no viable conversion from 'const char [4]' to 'String' (aka 'Box<basic_string<char> >')String a = "abc";``(编译器的错误消息可能会有所不同,但错误是相同的)。也许错误在于您不能使用隐式转换(通过调用构造函数或使用用户定义的强制类型转换运算符)与模板一起使用? - nefas
哎呀,你说得对。看来我在验证编译和不编译的内容时犯了一个错误。所以有一半的谜团解开了。我修改了问题。 - PhilLab
3个回答

18

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;
    /* ... */
};

演示1 演示2


我已经思考了3个小时,有经验的人会更好 :) - Soner from The Ottoman Empire
1
有什么理由限制为单个参数吗?例如,为什么不接受 Box<std::string> 的迭代器对? - MSalters
2
这个东西需要约束条件。很多约束条件。 - T.C.
@0x5453: 版本已改进 - Jarod42
Boxed(U0&& u0, Us&&... us),在 sizeof...(Us==0)&&std::is_same<std::decay_t<U0>,Boxed>{} 上禁用 - 添加 Boxed()=default;。现在即使 T 可以从 Boxed 构造,也不会用 T 构造替换复制/移动操作。 - Yakk - Adam Nevraumont
显示剩余3条评论

3

仅查看主函数部分的源代码:

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的构造函数从const char[3]构造一个std::string,这是可以的。

你的下一行代码:

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

是相同的std::vector<T>。因此,这也可以工作,因为你正在使用有效的std::string对象初始化vector<T>

在你的最后一行代码中:

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

在这里,你声明c是一个vector<T>,其中TBox<std::string>的一个typedef版本,但你没有使用Box<std::string>类型来初始化std::vector<T>。你试图用const char[3]对象或字符串字面量来初始化它。

你可以尝试在第三行中这样做:我没有尝试编译,但我认为它应该可以工作。

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

编辑 -- 我本意是使用 String 构造函数而不是 std::string,已做出相应的修改。


2
你可以使用一个帮助函数来创建相应的类型:
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");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接