嵌入模板时模板函数类型丢失

Templated function type lost when embedding templates

在玩弄 C++20 的概念时,我发现当创建一个描述函数应该如何的概念时(即 T 必须是一个以 size_t 作为参数的可调用函数),然后在另一个模板中使用该概念,函数的类型似乎“丢失”了。我真的没有很好的措辞方式,所以这是代码:

template<typename Func, typename ... Args>
concept FuncWithArgs = requires (Func f, Args... args) { f((size_t)1, args...); };

template<FuncWithArgs Func, typename ... Args>
void Foo(const Func& f, size_t i = 0, Args... args)
{
    f(i, args...);
}

然后调用它:

void MyFunc(size_t i, const std::string& s)
{ 
    std::cout << i << ", " << s << std::endl; 
}

int main(int argc, char **argv)
{
    Foo(MyFunc, 1, "asdf");

    return 0;
}

给出以下编译错误消息(使用 GCC 11.2 和 -std=c++20,https://godbolt.org/z/dvs9j77o3):

<source>: In function 'int main(int, char**)':
<source>:20:8: error: no matching function for call to 'Foo(void (&)(size_t, const string&), int, const char [5])'
   20 |     Foo(MyFunc, 1, "asdf");
      |     ~~~^~~~~~~~~~~~~~~~~~~
<source>:8:6: note: candidate: 'template<class Func, class ... Args>  requires  FuncWithArgs<Func> void Foo(const Func&, size_t, Args ...)'
    8 | void Foo(const Func& f, size_t i = 0, Args... args)
      |      ^~~
<source>:8:6: note:   template argument deduction/substitution failed:
<source>:8:6: note: constraints not satisfied
<source>: In substitution of 'template<class Func, class ... Args>  requires  FuncWithArgs<Func> void Foo(const Func&, size_t, Args ...) [with Func = void(long unsigned int, const std::__cxx11::basic_string<char>&); Args = {const char*}]':
<source>:20:8:   required from here
<source>:5:9:   required for the satisfaction of 'FuncWithArgs<Func>' [with Func = void()]
<source>:5:24:   in requirements with 'Func f', 'Args ... args' [with Args = {}; Func = void()]
<source>:5:59: note: the required expression 'f((size_t)(1), args ...)' is invalid
    5 | concept FuncWithArgs = requires (Func f, Args... args) { f((size_t)1, args...); };
      |                                                          ~^~~~~~~~~~~~~~~~~~~~
cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail

仔细观察,我首先看到 [with Func = void(long unsigned int, const std::__cxx11::basic_string<char>&); Args = {const char*}],紧接着是 [with Args = {}; Func = void()]。 Why/how Func 改变了吗?

经过更多挖掘,我想出了一个可行的解决方案 (https://godbolt.org/z/j6W7P895q):

template<typename Func, typename ... Args> 
requires requires (Func f, Args... args)    
{ 
        f((size_t)1, args...); 
}  
void Foo(const Func& f, size_t i = 0, Args... args) 
{
    f(i, args...);
}

由于使用的约束与我尝试使用 concept 时使用的约束相同,这让我相信函数的类型“丢失”并且概念本身被正确编写。

是我做的不对、已知问题还是模板限制?

当你这样写的时候:

template<typename Func, typename ... Args>
concept FuncWithArgs = requires (Func f, Args... args) { f((size_t)1, args...); };

template<FuncWithArgs Func, typename ... Args>
void Foo(const Func& f, size_t i = 0, Args... args)
{
    f(i, args...);
}

Foo的声明是shorthand为此:

template <typename Func, typename ... Args>
    requires FuncWithArgs<Func>
void Foo(const Func& f, size_t i = 0, Args... args)
{
    f(i, args...);
}

这可能清楚地表明出了什么问题。您要求该函数可以在没有 args 的情况下调用(好吧,实际上是 1 个 arg,size_t),但这不是您想要的。你想要这个:

template <typename Func, typename ... Args>
    requires FuncWithArgs<Func, Args...>
void Foo(const Func& f, size_t i = 0, Args... args)
{
    f(i, args...);
}

可以这样写 shorthand:

template <typename ... Args, FuncWithArgs<Args...> Func>
void Foo(const Func& f, size_t i = 0, Args... args)
{
    f(i, args...);
}