【问题标题】:Parameter pack list expansion causes variadic constructor overload to fail参数包列表扩展导致可变参数构造函数重载失败
【发布时间】:2016-08-12 11:19:20
【问题描述】:

我有一个带有两个可变参数构造函数的结构,区别在于第二个前面有一个 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*)&amp;i, 1, 2); 那么它也可以编译,所以 [A] 似乎阻止了从 int*int const* 的隐式转换,这通常适用于常规函数。

如果我不想在所有构造函数调用中显式 const 转换指针,而不是行 [A],我可以使用递归函数将参数包解压缩到 int 数组中,但这更麻烦。最简单的解决方案是将 [B] 更改为 template&lt;class... Args&gt; S(int *p, Args... args) {},但这隐藏了我不更改 *p 的意图。

【问题讨论】:

  • 你可以把int i变成constexpr int i吗?
  • 我可以将此示例更改为 int const i = 1 并且它可以工作,但一般来说,我并不总是传递 int const 的地址,通常我传递的是一个数组。

标签: c++


【解决方案1】:

之所以选择[A]是因为参数包可以完全匹配int*,所以不需要进行const转换就可以得到[B]。

一种选择是使用重载的帮助器来简单地提供一个“值获取器”,您可以在参数包上调用它:

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 的实现。我认为这个解决方案是一个更简单的标签调度。 Demo

【讨论】:

  • 对不起,我的例子不是很清楚,我实际上想要 [B] 的不同行为。通常,当我调用 [B] 时,我将一个 int 数组作为第一个参数传递,它是一堆数据,我希望将其与其他只是 int 值的 args 分开处理。我看到参数包可以匹配 int* 但我不认为这是 [B] 被忽略的原因,因为如果您注释掉行 [A] (只是行,离开封闭函数)然后 [B ] 确实被调用了。所以不知何故,是 [A] 行上存在初始化列表导致编译器忽略 [B]。
  • @GeorgeSkelton:如果你注释掉它会调用 [B] 的那一行是错误的。 Example。它调用 A 的原因是我所说的,这是参数列表中的精确类型匹配。如果您真的希望 [B] 被调用,那么您需要使 [A] 成为更差的候选人,您可以使用标签调度或 SFINAE 来做到这一点。
【解决方案2】:

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

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

如果您要传授大型课程,您可能需要考虑完美转发args

【讨论】:

  • 同意这行得通,但用户并不清楚被呼叫的是哪一个,以及他们的数据*p 是否受到保护。我需要研究完美的转发。
【解决方案3】:

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

你可以做的是使用 type_traits 来选择 ctor,像这样 (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]

【讨论】:

  • 缺少is_same 的第二个参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-23
  • 2022-01-17
  • 1970-01-01
  • 1970-01-01
  • 2021-09-14
  • 1970-01-01
相关资源
最近更新 更多