When playing around with C 20's concepts, I've found that when making a concept describing how a function should be (i.e. T must be a callable function that takes a size_t as argument), then using that concept in another template, the type of the function seems to be "lost". I don't really have a good way of phrasing it, so here's the code:
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...);
}
Then calling it with:
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;
}
Gives the following compilation error messages (using GCC 11.2 with -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
Looking closely at this, I first see [with Func = void(long unsigned int, const std::__cxx11::basic_string<char>&); Args = {const char*}], followed shortly after by [with Args = {}; Func = void()]. Why/how did Func change?
After more digging, I came up with a solution that works (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...);
}
Since the constraint used is identical to the one I tried doing with the concept, it leads me to believe that the type of the function is "lost" and that the concept itself is properly written.
Is it something that I am not doing right, a known issue, or a limitation of templates?
CodePudding user response:
When you write this:
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...);
}
The declaration of Foo is shorthand for this:
template <typename Func, typename ... Args>
requires FuncWithArgs<Func>
void Foo(const Func& f, size_t i = 0, Args... args)
{
f(i, args...);
}
Which probably makes it clear what went wrong. You're requiring that the function is callable with no args (well, really 1 arg, the size_t), but that's not what you want. You want this:
template <typename Func, typename ... Args>
requires FuncWithArgs<Func, Args...>
void Foo(const Func& f, size_t i = 0, Args... args)
{
f(i, args...);
}
which can be written using this shorthand:
template <typename ... Args, FuncWithArgs<Args...> Func>
void Foo(const Func& f, size_t i = 0, Args... args)
{
f(i, args...);
}
