【问题标题】:Is a c++11 variadic function template overload with a dependent type ambiguous?具有依赖类型的 c++11 可变参数函数模板重载是否不明确?
【发布时间】:2016-07-03 06:36:37
【问题描述】:

以下代码是递归可变参数函数重载的教科书示例。在 clang 和 GCC 中,它都能干净地编译,main 返回 36(如预期的那样):

template <typename T>
int add(T val)
{
    return val;
}

template <typename FirstTypeT, typename... RestT>
int add(FirstTypeT first_value, RestT... rest)
{
    return first_value + add<RestT...>(rest...);
}

int main(void)
{
    return add(12, 12, 12);
}

但是,这里稍作修改。它在模板定义中使用依赖类型,而不是直接使用模板参数:

struct Foo
{
    using SomeType = int;
};

template <typename T>
int add(typename T::SomeType val)
{
    return val;
}

template <typename FirstT, typename... RestT>
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest)
{
    return first_value + add<RestT...>(rest...);
}

int main(void)
{
    return add<Foo, Foo, Foo>(12, 12, 12);
}

它使用 GCC 5.2 按预期编译和运行,但 fails 使用 clang 3.8:

clang++ variadic.cpp -o var -std=c++11 -Wall
variadic.cpp:15:26: error: call to 'add' is ambiguous
    return first_value + add<RestT...>(rest...);
                         ^~~~~~~~~~~~~
variadic.cpp:15:26: note: in instantiation of function template specialization 'add<Foo, Foo>' requested here
    return first_value + add<RestT...>(rest...);
                         ^
variadic.cpp:20:12: note: in instantiation of function template specialization 'add<Foo, Foo, Foo>' requested here
    return add<Foo, Foo, Foo>(12, 12, 12);
           ^
variadic.cpp:7:5: note: candidate function [with T = Foo]
int add(typename T::SomeType val)
    ^
variadic.cpp:13:5: note: candidate function [with FirstT = Foo, RestT = <>]
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest)
    ^
1 error generated.

我的问题是双重的。

  1. 像typename RestT::SomeType... 中那样将作用域解析运算符应用于包的每个成员是否真的有效使用参数包类型名模式?
  2. clang 相对于标准是否正确,或者这是一个错误?第二个例子真的比第一个更模棱两可吗? (对于第一个示例,您似乎可以说单参数重载与用 RestT = &lt;&gt; 实例化的第二个参数不明确)

【问题讨论】:

  • MSVC15 编译这两个示例。 Clang 是一个奇怪的地方,但我没有一个标准的参考引用来说明谁是正确的,谁是错误的。
  • 基于这个缺陷(CWG1395),我认为这是一个clang bug。我找不到任何关于为什么嵌入类型会有所作为的任何信息。
  • 部分排序在这里很棘手,因为一切都在非推导的上下文中。
  • @T.C.不,这更容易,因为由于CWG 1391,所有这些对都被忽略了。

标签: c++ c++11 gcc clang language-lawyer


【解决方案1】:
  1. 是的,没关系。
  2. 目前的措辞非常明确:在部分排序期间完全忽略参数包,因为它没有参数 ([temp.deduct.partial]/(3.1))。 [temp.func.order]/5 也给出了一个非常切题的例子,即使有可推导的模板参数 - 表明你的第一个例子也是模棱两可的:

    [ 注意:由于调用上下文中的偏序只考虑具有显式调用参数的参数,因此忽略某些参数(即函数参数包、具有默认参数的参数、 和省略号参数)。 [...] [ 示例:

    template<class T, class... U> void f(T, U ...);  // #1
    template<class T            > void f(T       );  // #2
    
    void h(int i) {
      f(&i); // error: ambiguous
      // [...]
    }
    

    但是,这不是最优的。可变参数模板部分排序上有core issue 1395:

    CWG 同意该示例应该被接受,将这种情况作为迟到的决胜局处理,更喜欢省略参数而不是参数包。

    (Issue 1825 给出了更精细的策略。) 两个编译器都为第一种情况实现了这个规则;只有 GCC 对第二个有作用(即可以认为是领先了半步)。

【讨论】:

  • 嗯,我猜clang也实现了1395的方向,因为它可以用于推断的上下文案例。或许区别在于它没有实现1391?
  • @T.C. 1391 的决议是有争议的,而且可能很糟糕。不实施是有道理的。
  • 感谢您的回答,这正是我想要的。我可能会向 Clang 提交一个错误,因为他们似乎打算实现 1395(例如这个错误报告 llvm.org/bugs/show_bug.cgi?id=14372)
【解决方案2】:

错误信息已经说明了原因。

生成add(12)时,有两个可用的模板函数。那是

template <typename T>
int add(typename T::SomeType val);

和

template <typename FirstT, typename... RestT>
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest);
// and RestT is empty here(RestT = <>)

这不是标准用法,clang 是正确的。

考虑一下这段代码。

#include <tuple>
#include <type_traits>

struct Foo
{
    using SomeType = int;
};

// helper function to sum a tuple of any size
template<typename Tuple, std::size_t N>
struct TupleSum {
    typedef typename std::tuple_element<N - 1, Tuple>::type ref_t;
    typedef typename std::remove_reference<ref_t>::type noref_t;

    static noref_t sum(const Tuple& t) 
    {
        return std::get<N - 1>(t) + TupleSum<Tuple, N - 1>::sum(t);
    }
};

template<typename Tuple>
struct TupleSum<Tuple, 1> {
    typedef typename std::tuple_element<0, Tuple>::type ref_t;
    typedef typename std::remove_reference<ref_t>::type noref_t;

    static noref_t sum(const Tuple& t) 
    {
        return std::get<0>(t);
    }
};

template <typename... RestT>
int add(typename RestT::SomeType... rest) {
    typedef decltype(std::forward_as_tuple(rest...)) tuple_t;
    return TupleSum<tuple_t, sizeof...(RestT) >::sum(std::forward_as_tuple(rest...));
}

int main(void)
{
    return add<Foo, Foo, Foo>(12, 12, 12);
}

【讨论】:

  • 感谢您的回复。当然,错误消息解释了假定的问题。但正如我在问题中提到的,为什么第二个例子模棱两可,而第一个例子不是这样?为什么 GCC 会接受它?
  • 好的,在阅读您的编辑后,您似乎认为这是一个 GCC 错误。但是在第一个示例(以及许多其他可变参数函数示例)中也出现了同样明显的模棱两可的重载。那么这也无效吗?
  • 对不起,我误解了你的问题。我刚才读了N2242 和N2555,什么也没找到。这两个示例也适用于 MSVC 14。但我认为这仍然是一种已弃用的方法,因为 c++14 中的 std::integer_sequence 是作为解决类似问题的工具而发明的。
猜你喜欢
  • 1970-01-01
  • 2012-07-10
  • 2010-12-17
  • 1970-01-01
  • 2014-06-07
  • 2011-12-14
  • 1970-01-01
  • 2019-01-03
相关资源
最近更新 更多