问题
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:
- Who is correct in the first case?
- How to achieve the desired effect - i.e., how can I explicitly specify a (possibly empty) parameter pack?
回答1:
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.
回答2:
I suspect that the standard says the deduction of template argument packs must be "greedy", this would make MSVC++ wrong at accepting malformed code.
I don't want to concentrate on whether the compilers are wrong because type packs that are not the last in argument lists are very difficult to work with, so, I would rather focus on side stepping the cases where they arise.
The rules of the language dictate that explicit template arguments in a pack are just the beginning of the pack, not the whole pack, that means autodeduction rules will be activated the same. On the other hand, autodeduction of template parameters is "greedy", it keeps going regardless of what needs to be satisfied later. Autodeduction of template parameters do not "backtrack".
A way to sidestep the problem is to disable autodeduction and supply the template arguments explicitly.
What @Yakk does in his answer is to say that the argument type is a member of some template. At that moment the compiler turns off autodeduction because it can't deduce the template arguments from a member:
template<typename... Args>
void test_func(
const typename block<
std::function<void(int, Args..., char)>
>::type &argument
);
there, the rules of the language only allow to get the type of argument
by having the complete template parameter list, there is no deduction possible. The final type, typename block<...>::type
happens to be std::function<void(int, Args..., char)>
, but the compiler can't go from that "result" to the deduce the template arguments that produced it.
The type of argument
is calculated from the template parameter inputs.
来源:https://stackoverflow.com/questions/45948058/template-parameter-pack-deduction-when-not-passed-as-last-parameter