Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameter Pack Matching Rules with Multiple Packs

I am trying to write a function which takes another function, using parameter packs and some standard matching rules. As an example:

template <typename... TListElems, typename... TVectorElems>
void goal(void (*fn)(std::list<TListElems>..., std::vector<TVectorElems>...));

In order to disambiguate TListElems and TVectorElems, I added some std::tuple<T...>* so a caller can be explicit:

template <typename... TListElems, typename... TVectorElems>
void foo(std::tuple<TListElems...>*,
         std::tuple<TVectorElems...>*,
         void (*)(std::list<TListElems>..., std::vector<TVectorElems>...))
{
    // blah blah blah
}

void bar(std::list<int>, std::list<unsigned>, std::vector<float>, std::vector<double>)
{
    // blah blah blah
}

int main()
{
    foo((std::tuple<int, unsigned>*) nullptr,
        (std::tuple<float, double>*) nullptr,
        &bar);
}

Clang happily compiles this in the way I would expect, while g++ (7.2.1) gives the compilation error:

matching.cpp: In function ‘int main()’:
matching.cpp:20:13: error: no matching function for call to ‘foo(std::tuple<int, unsigned int>*, std::tuple<float, double>*, void (*)(std::list<int>, std::list<unsigned int>, std::vector<float>, std::vector<double>))’
         &bar);
             ^
matching.cpp:6:6: note: candidate: template<class ... TListElems, class ... TVectorElems> void foo(std::tuple<_Tps ...>*, std::tuple<_Elements ...>*, void (*)(std::list<TListElems>..., std::vector<TVectorElems>...))
 void foo(std::tuple<TListElems...>*,
      ^~~
matching.cpp:6:6: note:   template argument deduction/substitution failed:
matching.cpp:20:13: note:   mismatched types ‘std::vector<TVectorElems>’ and ‘std::list<int>’
         &bar);
             ^

In main, I would expect the call to foo to deduce TListElems as <int, unsigned> and TVectorElems as <float, double>, leading fn to be of type void (*)(std::list<int>, std::list<unsigned>, std::vector<float>, std::vector<double>) (the way things operate when there is only one pack or if I had manually written the overload).

§14.8.2.5/10 is the closest the Standard comes to explicitly preventing the foo example from working:

[Note: A function parameter pack can only occur at the end of a parameter-declaration-list (8.3.5). -end note]

The std::list<TListElems>... bit of fn seems like it would violate this note, but that's not entirely clear.

The question is: Who is right? GCC, Clang, or something else?

like image 598
Travis Gockel Avatar asked Dec 21 '17 22:12

Travis Gockel


2 Answers

I think clang is right here.

In void (*)(std::list<TListElems>..., std::vector<TVectorElems>...), TListElems... is a non-deduced context, which makes TVectorElems... also a non-deduced context. But both parameter packs are deducible from the two tuple pointer arguments, and it's expected to be able to just use that deduction result here too.

I filed gcc bug 83542.

like image 159
Barry Avatar answered Nov 20 '22 05:11

Barry


You may make happy both compilers with non deducible type:

template <typename T>
struct non_deducible {
    using type = T;  
};

template <typename T> using non_deducible_t = typename non_deducible<T>::type;


template <typename... TListElems, typename... TVectorElems>
void foo(std::tuple<TListElems...>*,
         std::tuple<TVectorElems...>*,
         void (*)(non_deducible_t<std::list<TListElems>>...,
                  non_deducible_t<std::vector<TVectorElems>>...))
{
    // blah blah blah
}

Demo

like image 24
Jarod42 Avatar answered Nov 20 '22 03:11

Jarod42