在一个可变参数函数中对可变参数模板类进行隐式转换。

huangapple go评论81阅读模式
英文:

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 &lt;functional&gt;
template&lt;class ResultType, class ... Args&gt;
void Foo( std::function&lt;ResultType(Args...)&gt; ) {}

void Dummy(int) {}

int main()
{
     Foo&lt;void, int&gt; ( std::function&lt;void(int)&gt;( Dummy ) ); // OK, no deduction and no conversion
     Foo( std::function&lt;void(int)&gt;( Dummy ) ); // OK, template argument deduction
     Foo&lt;void, int&gt;( 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&lt;void(int)&gt; ?

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&lt;class ... T&gt;
class Bar
{
public:
    // Non-explicit ctor, an int can go through implicit conversion
	Bar(int) {}
};

// A template function
template&lt;class T&gt;
void Xoo( Bar&lt;T&gt; ) {}

// Same, but this one has a variadic template
template&lt;class ... T&gt;
void Yoo( Bar&lt;T...&gt; ) {}

int main()
{
    Xoo( Bar&lt;bool&gt;( 100 ) ); //OK, argument deduction
    Xoo&lt;bool&gt;( 100 ); //OK, implicit conversion
    Yoo( Bar&lt;bool&gt;( 100 ) ); //OK, argument deduction
    Yoo&lt;bool&gt;( 100 ); // Not ok... ?
}

Output from GCC 9.2.0

prog.cc: In function &#39;int main()&#39;:
prog.cc:23:19: error: no matching function for call to &#39;Yoo&lt;bool&gt;(int)&#39;
   23 |    Yoo&lt;bool&gt;( 100 ); // Not ok... ?
      |                   ^
prog.cc:16:6: note: candidate: &#39;template&lt;class ... T&gt; void Yoo(Bar&lt;T ...&gt;)&#39;
   16 | void Yoo( Bar&lt;T...&gt; ) {}
      |      ^~~
prog.cc:16:6: note:   template argument deduction/substitution failed:
prog.cc:23:19: note:   mismatched types &#39;Bar&lt;T ...&gt;&#39; and &#39;int&#39;
   23 |    Yoo&lt;bool&gt;( 100 ); // Not ok... ?
      |                   ^

Output from clang 9.0.0

prog.cc:23:4: error: no matching function for call to &#39;Yoo&#39;
   Yoo&lt;bool&gt;( 100 ); // Not ok... ?
   ^~~~~~~~~
prog.cc:16:6: note: candidate template ignored: could not match &#39;Bar&lt;bool, type-parameter-0-0...&gt;&#39; against &#39;int&#39;
void Yoo( Bar&lt;T...&gt; ) {}
     ^
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 &lt;functional&gt;
// Not variadic this time
template&lt;class ResultType, class Arg&gt;
void Goo( std::function&lt;ResultType(Arg)&gt; ) {}
void Dummy(int) {}
int main()
{
     Goo&lt;void, int&gt; ( Dummy ); // Ok this time
}

Interestingly, the following modification makes it compile in clang

[...]

// Same, but this one has a variadic template
template&lt;class ... T&gt;
void Yoo( Bar&lt;T..., bool&gt; ) {}
//                  ^^^^
// 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&lt;class ResultType, class ... Args&gt;
void Foo( std::function&lt;ResultType(Args...)&gt; ) {}

Foo&lt;void, int&gt; ( std::function&lt;void(int)&gt;( Dummy ) ); // OK, no deduction and no conversion
Foo( std::function&lt;void(int)&gt;( Dummy ) ); // OK, template argument deduction
Foo&lt;void, int&gt;( Dummy ); // Compile error

Foo&lt;void,int&gt; 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&lt;void, int&gt; ( std::function&lt;void(int,int)&gt;( nullptr) );

you'll see we passed in void,int, but what was deduced was void,int,int -- two ints 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&lt;T...,bool&gt; 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&lt;class T&gt;
struct identity { using type=T; };
template&lt;class T&gt; using identity_t = typename identity&lt;T&gt;::type;

template&lt;class ResultType, class ... Args&gt;
void Foo( identity_t&lt;std::function&lt;ResultType(Args...)&gt;&gt; ) {}

which also blocks template argument deduction.

Now

Foo( std::function&lt;void(int)&gt;( 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&lt;class ResultType, class ... Args&gt;
void Foo( identity_t&lt;std::function&lt;ResultType(Args...)&gt;&gt; ) {}

struct never_use {};
template&lt;class R0=never_use, class ResultType, class ... Args&gt;
requires (std::is_same_v&lt;R0, never_use&gt;)
void Foo( std::function&lt;ResultType(Args...)&gt; ) {}

but this doesn't support passing partial arguments without even more tomfoolery.

huangapple
  • 本文由 发表于 2020年1月3日 13:31:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/59573624.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定