英文:
Implicit conversion of a variadic template class in a variadic function
问题
在第三个示例中,为什么没有从void (*)(int)
到std::function<void(int)>
的显式转换?
我查找了一些答案,但这些答案通常涉及到模糊的重载解析或模板参数推断,而不是您提出的问题。
对于第一个示例,问题在于Foo<void, int>(Dummy)
的函数调用中,模板参数被显式指定为<void, int>
,这会导致编译错误,因为此时不再进行模板参数推断。这并不是因为缺少显式转换,而是因为模板参数被明确指定,编译器不会尝试进行类型转换。
对于第二个示例,问题涉及到模板参数包的处理。在Yoo<bool>(100)
的调用中,您试图将int
隐式转换为Bar<bool>
,但编译器无法确定如何将int
映射到Bar<bool>
,因此导致编译错误。与第一个示例一样,这不是因为缺少显式转换,而是因为编译器无法找到匹配的模板参数。
在您的修改中,添加了额外的bool
模板参数,似乎使编译器能够正确地进行隐式转换,因为它知道应该将int
映射到Bar<bool, int>
。
总之,这些问题与模板参数推断和模板参数包的处理有关,而不是显式转换。如果您需要更多关于这些问题的详细信息,可能需要深入研究C++模板元编程的更高级方面。
英文:
Consider the following code
#include <functional>
template<class ResultType, class ... Args>
void Foo( std::function<ResultType(Args...)> ) {}
void Dummy(int) {}
int main()
{
Foo<void, int> ( std::function<void(int)>( Dummy ) ); // OK, no deduction and no conversion
Foo( std::function<void(int)>( Dummy ) ); // OK, template argument deduction
Foo<void, int>( Dummy ); // Compile error
}
In the third one I understand that a template deduction cannot take place, that's why the template argument is explicitly specified. But why there isn't an explicit conversion from void (*)(int)
to std::function<void(int)>
?
I looked up for answers but these are about ambiguous overloading resolution or template deductions, not the topic in question.
<https://stackoverflow.com/questions/5931214/isnt-the-template-argument-the-signature-of-stdfunction-part-of-its-type>
<https://stackoverflow.com/questions/39173519/template-type-deduction-with-stdfunction>
<https://stackoverflow.com/questions/6193734/implicit-conversions-with-stdfunction>
Then I tried to test with my own template class instead of std::function.
// Variadic template class
template<class ... T>
class Bar
{
public:
// Non-explicit ctor, an int can go through implicit conversion
Bar(int) {}
};
// A template function
template<class T>
void Xoo( Bar<T> ) {}
// Same, but this one has a variadic template
template<class ... T>
void Yoo( Bar<T...> ) {}
int main()
{
Xoo( Bar<bool>( 100 ) ); //OK, argument deduction
Xoo<bool>( 100 ); //OK, implicit conversion
Yoo( Bar<bool>( 100 ) ); //OK, argument deduction
Yoo<bool>( 100 ); // Not ok... ?
}
Output from GCC 9.2.0
prog.cc: In function 'int main()':
prog.cc:23:19: error: no matching function for call to 'Yoo<bool>(int)'
23 | Yoo<bool>( 100 ); // Not ok... ?
| ^
prog.cc:16:6: note: candidate: 'template<class ... T> void Yoo(Bar<T ...>)'
16 | void Yoo( Bar<T...> ) {}
| ^~~
prog.cc:16:6: note: template argument deduction/substitution failed:
prog.cc:23:19: note: mismatched types 'Bar<T ...>' and 'int'
23 | Yoo<bool>( 100 ); // Not ok... ?
| ^
Output from clang 9.0.0
prog.cc:23:4: error: no matching function for call to 'Yoo'
Yoo<bool>( 100 ); // Not ok... ?
^~~~~~~~~
prog.cc:16:6: note: candidate template ignored: could not match 'Bar<bool, type-parameter-0-0...>' against 'int'
void Yoo( Bar<T...> ) {}
^
1 error generated.
Why, if the function has variadic template, implicit conversion doesn't take place (even when the template arguments are explicitly specified)?
I went back to std::function and, sure enough, if the function doesn't have a variadic template, it works.
#include <functional>
// Not variadic this time
template<class ResultType, class Arg>
void Goo( std::function<ResultType(Arg)> ) {}
void Dummy(int) {}
int main()
{
Goo<void, int> ( Dummy ); // Ok this time
}
Interestingly, the following modification makes it compile in clang
[...]
// Same, but this one has a variadic template
template<class ... T>
void Yoo( Bar<T..., bool> ) {}
// ^^^^
// An extra template for Bar makes implicit conversion
// work for some reason
[...]
I tried looking for more answers related to variadic templates but there are either not about this specific topic or too advance for me to understand at this point.
<https://stackoverflow.com/questions/40475630/how-to-overload-variadic-templates-when-theyre-not-the-last-argument/40476083#40476083>
<https://stackoverflow.com/questions/45948058/template-parameter-pack-deduction-when-not-passed-as-last-parameter>
<https://stackoverflow.com/questions/43430921/deduction-guides-and-variadic-class-templates-with-variadic-template-constructor>
<https://stackoverflow.com/questions/42333734/template-argument-and-deduction-of-stdfunction-parameters>
<https://stackoverflow.com/questions/43430921/deduction-guides-and-variadic-class-templates-with-variadic-template-constructor>
答案1
得分: 1
template<class ResultType, class ... Args>
void Foo(std::function<ResultType(Args...)>) {}
Foo<void, int>(std::function<void(int)>(Dummy)); // OK, no deduction and no conversion
Foo(std::function<void(int)>(Dummy)); // OK, template argument deduction
Foo<void, int>(Dummy); // Compile error
"Foo<void, int>"并不会产生您想要的效果。
您可能认为它明确指定了Foo
的模板参数。但实际上它只是声明Foo
的模板参数以void
开头,然后是一个int
,然后...没有明确指定其他内容。
因此,模板参数推导仍然会运行以确定其余的参数是什么。但它失败了。然后你的编译器会报错。
如果您这样做:
Foo<void, int>(std::function<void(int, int)>(nullptr));
您会发现我们传入了void, int
,但被推导出来的是void, int, int
——两个int
而不是一个。
...
对于您的特定问题,您正在混合使用模板参数推导和类型擦除类型(std::function
),同时进行这两种操作就好像因为您想要剥离掉油漆而把车漆成白色一样。
模板参数推导和类型擦除的操作是不完美的互补操作。
当存在可变参数包时,一旦传递了每个参数,就不再执行模板参数推导,因此不再执行模板参数推导。
在您的Bar<T..., bool>
的情况下,您正在阻止参数推导,因为C++在可变参数包后面有其他内容时拒绝推导包。
如果您真的想要这个,可以这样做:
template<class T>
struct identity { using type = T; };
template<class T> using identity_t = typename identity<T>::type;
template<class ResultType, class ... Args>
void Foo(identity_t<std::function<ResultType(Args...)>>) {}
这也会阻止模板参数推导。
现在:
Foo(std::function<void(int)>(Dummy)); // OK, template argument deduction
不再起作用,因为它拒绝推导参数。
您可以使用一些花招来同时支持这两种情况:
template<class ResultType, class ... Args>
void Foo(identity_t<std::function<ResultType(Args...)>>) {}
struct never_use {};
template<class R0 = never_use, class ResultType, class ... Args>
requires (std::is_same_v<R0, never_use>)
void Foo(std::function<ResultType(Args...)>) {}
但这不支持传递部分参数,除非使用更多花招。
英文:
template<class ResultType, class ... Args>
void Foo( std::function<ResultType(Args...)> ) {}
Foo<void, int> ( std::function<void(int)>( Dummy ) ); // OK, no deduction and no conversion
Foo( std::function<void(int)>( Dummy ) ); // OK, template argument deduction
Foo<void, int>( Dummy ); // Compile error
Foo<void,int>
doesn't do what you think it does.
You think it explicitly specifies the template arguments to Foo
. What it actually does is state that the template arguments for Foo
start with void
then an int
, and ... then it says nothing.
So template argument deduction still runs to find out what the rest of the arguments are. It fails. Then your compiler complains.
To see this
Foo<void, int> ( std::function<void(int,int)>( nullptr) );
you'll see we passed in void,int
, but what was deduced was void,int,int
-- two int
s not one.
...
For your particular problem, you are mixing template argument deduction with a type erasure type (std::function
), and doing them both is a lot like painting a car in because you want to peel the paint off.
The operations of template argument deduction and type erasure are imperfect inverses of each other.
When there is a variartic pack left, there is no deduction to do once you pass every argument, so it no longer does template argument deduction.
In the case of your Bar<T...,bool>
you are blocking argument deduction because C++ refuses to deduce packs when there is anything after them.
If you really want this, you can do:
template<class T>
struct identity { using type=T; };
template<class T> using identity_t = typename identity<T>::type;
template<class ResultType, class ... Args>
void Foo( identity_t<std::function<ResultType(Args...)>> ) {}
which also blocks template argument deduction.
Now
Foo( std::function<void(int)>( Dummy ) ); // OK, template argument deduction
doesn't work, as it refuses to deduce the argument.
You can support both with a bit of tomfoolery:
template<class ResultType, class ... Args>
void Foo( identity_t<std::function<ResultType(Args...)>> ) {}
struct never_use {};
template<class R0=never_use, class ResultType, class ... Args>
requires (std::is_same_v<R0, never_use>)
void Foo( std::function<ResultType(Args...)> ) {}
but this doesn't support passing partial arguments without even more tomfoolery.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论