Is a c++11 variadic function template overload with a dependent type ambiguous?

匆匆过客 提交于 2021-02-06 11:06:58

问题


The following code is a textbook example of a recursive variadic function overload. In both clang and GCC, it compiles cleanly, and main returns 36 (as expected):

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);
}

However, here is a slight modification. It uses a dependent type in the template definition instead of the template parameter directly:

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);
}

It compiles and runs as intended using GCC 5.2, but fails using 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.

My question is twofold.

  1. Is it really a valid use of a parameter pack typename pattern to apply the scope resolution operator to each member of the pack as in typename RestT::SomeType... ?
  2. Is clang correct vis-à-vis the standard, or is this a bug? Is the second example really any more ambiguous than the first? (For the first example, it seems like you could say that the single argument overload is ambiguous with the the second instantiated with RestT = <>)

回答1:


  1. Yes, that's fine.
  2. Current wording is quite clear on this: The parameter pack is completely ignored during partial ordering, because there are no arguments for it ([temp.deduct.partial]/(3.1)). [temp.func.order]/5 also gives a very on point example, even with deducible template arguments - indicating that your first example is also ambiguous:

    [ Note: Since partial ordering in a call context considers only parameters for which there are explicit call arguments, some parameters are ignored (namely, function parameter packs, parameters with default arguments, and ellipsis parameters). [...] [ Example:

    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
      // [...]
    }
    

    However, this is not optimal. There is core issue 1395 on variadic template partial ordering:

    CWG agreed that the example should be accepted, handling this case as a late tiebreaker, preferring an omitted parameter over a parameter pack.

    (Issue 1825 gives a more refined strategy.) Both compilers implement this rule for the first case; Only GCC does for the second one (i.e. can be considered half a step ahead).




回答2:


The error message has already shown the reason.

When generate add(12), there are two available template functions. That's

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

and

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

This is not a standard usage and clang is correct.

Consider this code.

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


来源:https://stackoverflow.com/questions/36051645/is-a-c11-variadic-function-template-overload-with-a-dependent-type-ambiguous

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!