可变参数模板与初始化列表无法正常工作

4
我创建了一个工厂函数模板:
template <typename M, typename... Args>
std::shared_ptr<M> create(Args... args)
{
    return std::make_shared<M>(args...);
}

还有一个简单的容器:

struct Group {
    std::vector<int> vec;
    Group(std::initializer_list<int> il) : vec(il) {}
};

然后我尝试创建一个群组

int main()
{
    auto gr = create<Group>({1, 2, 3});
    return 0;
}

这段代码无法编译,

error: no matching function for call to 'create'
    auto gr = create<Group>({1, 2, 3});
candidate function not viable: requires 0 arguments, but 1 was provided
std::shared_ptr<M> create(Args... args)
                   ^

但如果我使用一个临时变量:

int main(int argc, char *argv[])
{
    std::initializer_list<int> il = {1, 2, 3};
    auto gr = create<Group>(il);
    return 0;
}

它确实可以。为什么呢?
针对这种情况,建议采取哪种解决方案?

认为问题在于当{ 1,2,3 }传递给create()函数时,编译器实际上不知道该怎么处理:它需要确定一个合适的类型,但是没有关于应该是什么类型的指导。如果你使用了std :: initializer_list {1,2,3}或者重载了create()以接收std :: initailizer_list <T>(其中T是模板参数),那么它应该可以工作。 - Dietmar Kühl
2个回答

5

一个模板参数不能从初始化列表中推导出来(这是一个非推导上下文),但可以从类型为std::initializer_list<something>的表达式中推导出来。这两者并不相同。

[temp.deduct.call]/1 Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list<P'> for some P' and the argument is an initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (14.8.2.5). [ Example:

template<class T> void f(std::initializer_list<T>);
f({1,2,3}); // T deduced to int
f({1,"asdf"}); // error: T deduced to both int and const char*

template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T

—end example ]


这种情况的推荐解决方案是什么? - marmistrz
其实没有什么。std::make_shared 也无法处理这种情况。调用者需要一些解决方法,就像你发现的那样。如果您不介意我问,为什么要重新发明 std::make_shared?为什么不直接调用它呢? - Igor Tandetnik

1

来自cppreference:

花括号初始化列表不是表达式,因此没有类型,例如decltype({1,2})是不合法的。没有类型意味着模板类型推导不能推导出与花括号初始化列表相匹配的类型,因此给定声明template<class T> void f(T);,表达式f({1,2,3})是不合法的。

但有一个例外:auto a = { 1, 2 };会导致a成为std::initializer_list。但这仅适用于auto类型推导,而不适用于模板。


这种情况的推荐解决方案是什么? - marmistrz

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