2016-03-17 44 views
9

下面的代码是一个递归可变参数函数过载的一个典型例子。在这两种铛和GCC,它编译干净,并main返回36(如预期):是一个C++ 11 variadic函数模板重载与依赖类型模糊?

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使用铛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. 是铿锵正确的面对面的人的标准,或者这是一个错误?第二个例子真的比第一个更模糊吗? (对于第一个例子,好像你可以说,一个参数超载是暧昧与第二与RestT = <>实例化)
+1

MSVC15编译两个样本。铿锵在这里很奇怪,但我没有一个标准的参考文献引用来说谁是正确的,谁是错的。 – Niall

+2

基于这个缺陷([CWG1395](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1395)),我认为这是一个叮叮声虫。我无法找到为什么嵌入式会产生变化的任何事情。 – Niall

+1

部分排序在这里很棘手,因为所有内容都在非推导的上下文中。 –

回答

7
  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为第二个(即可以被认为是半步)。

+0

嗯,我想那个clang也实现了1395的方向,因为它可以与推断的上下文情况一致。也许差别在于它没有实现1391? –

+0

@ T.C。 1391的决议是有争议的,可能是不好的。没有实施是有道理的。 – Columbo

+0

感谢您的回答,那正是我一直在寻找的。 我可能会提交一个Clang的错误,因为他们似乎打算实施1395(例如给出这个错误报告https://llvm.org/bugs/show_bug.cgi?id=14372) –

0

错误信息已经显示出的原因。

当生成加载(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 = <>) 

这不是一个标准的使用和铛是正确的。

请考虑这段代码。

#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); 
} 
+1

感谢您的回复。当然,错误消息解释了所谓的问题。但正如我在问题中提到的那样,为什么第二个例子不明确,第一个例子不是这种情况?为什么GCC接受它? –

+0

好吧,在阅读你的编辑之后,它看起来像你认为它是一个GCC错误。但是第一个例子(以及许多可变参数函数的例子)中出现了同样明显的模糊过载。那也是无效的吗? –

+1

对不起,我误解了你的问题。我读了[N2242](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2242.pdf)和[N2555](http://www.open-std.org /jtc1/sc22/wg21/docs/papers/2008/n2555.pdf),但什么都没发现。这两个示例也适用于MSVC 14.但我认为它仍然是一个不赞成使用的方法,因为C++ 14中的std :: integer_sequence是作为解决类似问题的工具发明的。 – owent