Search code examples
c++templatesc++20c++-concepts

Constrain function return type without specifying function parameters


I was trying to constrain the return type of client-provided functions without asking to specify the function parameters:

#include <type_traits>

template <typename T>
struct ReturnTypeWrapper {};

template <typename R, typename... Args>
struct ReturnTypeWrapper<R(Args...)> {
    using type = R;
};

template <typename T>
concept ReturnVoid = std::is_same_v<void, typename ReturnTypeWrapper<T>::type>;

template <auto c>
requires(ReturnVoid<decltype(c)>)
void g() {}

void foo(double, int){};

int main() {
    static_assert(std::is_same_v<void, typename ReturnTypeWrapper<decltype(foo)>::type>);
    g<foo>();
    return 0;
}

While there is no problem with the static_assert, when it comes to instantiating g with the foo function, I have the following compile error:

xxx/Concept.cpp: In function ‘int main()’:
xxx/Concept.cpp:24:11: error: no matching function for call to ‘g<foo>()’
   24 |     g<foo>();
      |     ~~~~~~^~
xxx/Concept.cpp:17:6: note: candidate: ‘template<auto c>  requires  ReturnVoid<decltype(c)> void g()’
   17 | void g() {}
      |      ^
xxx/Concept.cpp:17:6: note:   template argument deduction/substitution failed:
xxx/Concept.cpp:17:6: note: constraints not satisfied
xxx/Concept.cpp: In substitution of ‘template<auto c>  requires  ReturnVoid<decltype(c)> void g() [with auto c = foo]’:
xxx/Concept.cpp:24:11:   required from here
xxx/Concept.cpp:13:9:   required for the satisfaction of ‘ReturnVoid<decltype (c)>’ [with c = &foo()]
xxx/Concept.cpp:13:27: error: no type named ‘type’ in ‘struct ReturnTypeWrapper<void (*)(double, int)>’
   13 | concept ReturnVoid = std::is_same_v<void, typename ReturnTypeWrapper<T>::type>;
      |                      ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
make[3]: *** [cpp/Template/CMakeFiles/template-concept.dir/build.make:76: cpp/Template/CMakeFiles/template-concept.dir/Concept.cpp.o] Error 1
make[2]: *** [CMakeFiles/Makefile2:5580: cpp/Template/CMakeFiles/template-concept.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:5587: cpp/Template/CMakeFiles/template-concept.dir/rule] Error 2
make: *** [Makefile:1941: template-concept] Error 2

It appears that only the primary template is chosen rather than the specialized one for function pointers. Why is it so? Am I on the right track to constrain function return type without specifying parameters?

PS: Compiler is gcc 11.3.0.


Solution

  • template <auto c>
        requires (ReturnVoid<decltype(c)>)
    void g() {}
    

    The issue here is that when you call g<foo>(), what you get for c isn't void(double, int) - because you can't have values of function style. You get a function-to-pointer conversion, so c is actually a void(*)(double, int). This doesn't match your specialization for ReturnTypeWrapper which only handled the pattern R(Args...).