参数包列表展开导致可变构造函数重载失败

Parameter pack list expansion causes variadic constructor overload to fail

本文关键字:构造函数 失败 重载 包列表 列表 参数      更新时间:2023-10-16

我有一个具有两个可变构造函数的结构体,其区别在于在第二个构造函数的前面存在一个单独的int const*参数:

struct S
{
    template<class... Args> S(Args... args)
    {
        int arr[sizeof...(args)] = { args... }; // [A]
    }
    template<class... Args> S(int const *p, Args... args) {} // [B]
};
int main()
{   
    int i = 1;
    S s(&i, 1, 2); // [C]
    return 0;
}

使用Visual c++ 2015,行[A]导致:error C2440: 'initializing': cannot convert from 'int *' to 'int'。如果我注释掉行[A],那么它编译得很好,[C]调用[B],所以似乎初始化列表的存在导致编译器查看函数内部并贪婪地尝试将所有参数转换为int,忽略解释[B]。

如果我保留[A],但将[C]中的构造函数调用更改为S s((int const*)&i, 1, 2);,那么它也可以编译,因此[A]似乎可以防止从int*int const*的隐式转换,这通常适用于常规函数。

如果我不想在所有构造函数调用中显式地对指针进行const强制转换,而不是使用[A]行,我可以使用递归函数将参数pack解包为int数组,但这样更混乱。最简单的解决方案是将[B]更改为template<class... Args> S(int *p, Args... args) {},但这样就隐藏了我不更改*p的意图。

选择[A]的原因是参数包可以完全匹配int*,因此不需要const转换就可以得到[B]。

一个选择是使用一个重载的帮助器来简单地提供一个"值getter",你可以在参数包上调用它:

int get_value_impl(int _in)
{
   return _in;
}
int get_value_impl(const int* _in)
{
   return *_in;
}

struct S
{
    template<class... Args> S(Args... args)
    {
        int arr[sizeof...(args)] = { get_value_impl(args)... }; // [A]
    }
};
int main()
{   
    int i = 1;
    S s(&i, 1, 2); // [C]
    return 0;
}

现在你根本不需要[B]了。

如果你有更多的类型,你可以添加更多的重载到get_value_impl,你将保留S的结构。如果事情变得比这更复杂,您可能需要考虑使用SFINAE来选择get_value_impl的实现。我认为这种解决方案是一种更简单的标记分派。演示

重载可变模板会变得非常糟糕。一种选择是添加一个重载,它接受一个int*并委托给int const*构造函数:

template<class... Args> S(int *p, Args... args) : 
    S(static_cast<int const*>(p), args...) { }

在这种情况下,如果你要传递大的类,你可能要考虑完美转发args

这是因为它选择的是第一个构造函数,而不是第二个。原因是&i绑定了int* const的引用。

你能做的是使用type_traits来选择变量,像这样(c++ 14):

template<typename First, typename ...Args> 
S(T && t, Args && ...args) : 
       S(  std::is_same<int,std::remove_reference_t<std::remove_cv<T>>>(), 
           std::forward<T>(t), std::forward<Args>(args)...) {}
template<typename ...Args>
S(std::false_type, int* const p, Args&&...args) //[A]
template<typename ...Args>
S(std::true_type, Args&&...args) //[B]