Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template parameter pack deduction when not passed as last parameter

Consider the following code:

#include <iostream>
#include <functional>

template<typename... Args>
void testfunc(const std::function<void (float, Args..., char)>& func)
{

}

int main(int argc, char* argv[])
{
    auto func = [](float, int, char) {};
    auto sfunc = static_cast<std::function<void (float, int, char)>>(func);
    testfunc<int>(sfunc);

    return 0;
}

I specify the type explicitly because (https://stackoverflow.com/a/40476083):

When a parameter pack doesn't appear last in the parameter declaration, it is a non-deduced context. A non-deduced context means that the template arguments have to be given explicitly.

MSVC successfully compiles it, while both gcc and clang reject the code:

source_file.cpp: In function ‘int main(int, char**)’:
source_file.cpp:14:24: error: no matching function for call to ‘testfunc(std::function<void(float, int, char)>&)’
     testfunc<int>(sfunc);
                        ^
source_file.cpp:5:6: note: candidate: template<class ... Args> void testfunc(const std::function<void(float, Args ..., char)>&)
 void testfunc(const std::function<void (float, Args..., char)>& func)
      ^
source_file.cpp:5:6: note:   template argument deduction/substitution failed:
source_file.cpp:14:24: note:   mismatched types ‘char’ and ‘int’
     testfunc<int>(sfunc);
                        ^
source_file.cpp:14:24: note:   ‘std::function<void(float, int, char)>’ is not derived from ‘const std::function<void(float, Args ..., char)>’

Let's now make a slight change - let's remove the int argument from our local func, thereby causing the template argument pack to become empty:

#include <iostream>
#include <functional>

template<typename... Args>
void testfunc(const std::function<void (float, Args..., char)>& func)
{

}

int main(int argc, char* argv[])
{
    auto func = [](float, char) {};
    auto sfunc = static_cast<std::function<void (float, char)>>(func);
    testfunc<>(sfunc);

    return 0;
}

This time, all three compilers reject the code as incorrect. Tested with http://rextester.com/l/cpp_online_compiler_gcc and a local Visual Studio installation.

Questions:

  1. Who is correct in the first case?
  2. How to achieve the desired effect - i.e., how can I explicitly specify a (possibly empty) parameter pack?
like image 987
user4520 Avatar asked Aug 29 '17 20:08

user4520


1 Answers

We can block deduction:

template<typename... Args>
void testfunc(const block_deduction<std::function<void (float, Args..., char)>>& func)

with

template<class T>
struct tag_t{using type=T;};

template<class T>
using block_deduction=typename tag_t<T>::type;

and now Args... is in a non-deduced context.

You can do fancier things with SFINAE and omitting char then testing that char is at the end of the Args..., but that seems overkill.


I will bet dollars to donuts that when gcc and clang disagree with MSVC, MSVC isn't right. But I have not standard delved to confirm that.

like image 52
Yakk - Adam Nevraumont Avatar answered Sep 27 '22 19:09

Yakk - Adam Nevraumont