【问题标题】:Implicit conversion of a variadic template class in a variadic function可变参数函数中可变参数模板类的隐式转换
【发布时间】:2020-04-21 17:24:50
【问题描述】:

考虑下面的代码

#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
}

在第三个中,我理解不能进行模板推导,这就是显式指定模板参数的原因。 但是为什么没有从void (*)(int)std::function&lt;void(int)&gt; 的显式转换呢?

我查找了答案,但这些是关于模棱两可的重载解决方案或模板推导,而不是相关主题。

Isn't the template argument (the signature) of std::function part of its type?

Template type deduction with std::function

Implicit conversions with std::function

然后我尝试使用自己的模板类而不是 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... ?
}

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... ?
      |                   ^

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.

为什么,如果函数具有可变参数模板,则不会进行隐式转换(即使显式指定了模板参数)? 我回到 std::function ,果然,如果函数没有可变参数模板,它就可以工作。

#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
}

有趣的是,下面的修改使它可以在 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

[...]

我尝试寻找更多与可变参数模板相关的答案,但要么没有关于这个特定主题,要么太超前,我现在无法理解。

How to overload variadic templates when they're not the last argument

Template parameter pack deduction when not passed as last parameter

Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths

Template argument and deduction of std::function parameters

Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths

【问题讨论】:

  • 请注意,这会在 MSVC 中编译,但智能感知会显示红色曲线。
  • @P.Rodriguez 在这里回答:stackoverflow.com/a/59578078/5632316
  • @KaenbyouRin 是的,这正是我要找的。谢谢:)

标签: c++ templates variadic-templates variadic-functions implicit-conversion


【解决方案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&lt;void,int&gt; 并没有按照你的想法去做。

您认为它明确指定了Foo 的模板参数。它实际上所做的是声明Foo 的模板参数 void 开头,然后是int,然后......然后它什么也没说。

所以模板参数推导仍然运行以找出其余参数是什么。它失败。然后你的编译器会抱怨。

看到这个

 Foo<void, int> ( std::function<void(int,int)>( nullptr) );

你会看到我们通过了void,int,但推断出的是void,int,int——两个int不是一个。

...

对于您的特定问题,您将模板参数推导与类型擦除类型 (std::function) 混合使用,并且两者都做很像给汽车涂漆,因为您想剥掉油漆。

模板参数推导和类型擦除的操作是不完美的

当剩下一个变量包时,一旦你传递了每个参数,就不需要做任何推导,所以它不再做模板参数推导。

在您的 Bar&lt;T...,bool&gt; 的情况下,您正在阻止参数推导,因为 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...)> ) {}

但是这不支持在没有更多愚蠢的情况下传递部分参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    • 1970-01-01
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多