Home > Back-end >  C Type deduction on template specialization fails on void parameter
C Type deduction on template specialization fails on void parameter

Time:09-17

I created a template class, where the constructor takes a std::function object. The first template parameter indicates the return value of that function. The second parameter defines the type of the parameter of that function.

#include <functional>

//Base
template<class R, class Arg>
class Executor {
    public:      
        Executor(std::function<R(Arg)> function) 
            : mFunction(function)  
        {}

    private:
        std::function<R(Arg)> mFunction;
};

//Specialization1
template<class Arg>
class Executor<void, Arg> {
    public:      
        Executor(std::function<void(Arg)> function) 
            : mFunction(function)  
        {}

    private:
        std::function<void(Arg)> mFunction;
};

//Specialization2
template<class R>
class Executor<R, void> {
    public:      
        Executor(std::function<R()> function) 
            : mFunction(function)  
        {}

    private:
        std::function<R()> mFunction;
};

int testBase(float value) {
    return 5;
}

void testSpecialization1(float value) {}

int testSpecialization2() {
    return 22;
}

int main() {
    Executor executorBase{std::function(testBase)};
    
    Executor executorSpecialization1{std::function(testSpecialization1)};
    
    //Executor<int, void> executorSpecialization2{std::function(testSpecialization2)}; // Compiles
    Executor executorSpecialization2{std::function(testSpecialization2)}; // Doesn't compile. Uses Base. template<class R, class Arg>
}

On using a function std::function<int()>, see executorSpecialization2, the compiler complains with:

main.cpp: In function 'int main()':
main.cpp:55:72: error: class template argument deduction failed:
   55 |     Executor executorSpecialization2{std::function(testSpecialization2)}; // Doesn't compile. Uses Base. template<class R, class Arg>
      |                                                                        ^
main.cpp:55:72: error: no matching function for call to 'Executor(std::function<int()>)'
main.cpp:7:9: note: candidate: 'template<class R, class Arg> Executor(std::function<R(Arg)>)-> Executor<R, Arg>'
    7 |         Executor(std::function<R(Arg)> function)
      |         ^~~~~~~~
main.cpp:7:9: note:   template argument deduction/substitution failed:
main.cpp:55:72: note:   candidate expects 1 argument, 0 provided
   55 |     Executor executorSpecialization2{std::function(testSpecialization2)}; // Doesn't compile. Uses Base. template<class R, class Arg>
      |                                                                        ^
main.cpp:5:7: note: candidate: 'template<class R, class Arg> Executor(Executor<R, Arg>)-> Executor<R, Arg>'
    5 | class Executor {
      |       ^~~~~~~~
main.cpp:5:7: note:   template argument deduction/substitution failed:
main.cpp:55:72: note:   'std::function<int()>' is not derived from 'Executor<R, Arg>'
   55 |     Executor executorSpecialization2{std::function(testSpecialization2)}; // Doesn't compile. Uses Base. template<class R, class Arg>
      |

It tries with the base version. But the second parameter is of course missing. If I specify the template parameter it compiles.

So why the base template is chosen for executorSpecialization2? Is it even possible to use type deduction on void here without the need of passing the template parameter?

Thanks

CodePudding user response:

When you use the name Executor without explicit template arguments like in Executor<int, float>, C usually tries to figure out what you mean by class template argument deduction (or CTAD). This process doesn't look at any class template specializations (partial or explicit), only the primary template.

The primary template has the constructor Executor(std::function<R(Arg)> function);. This is good enough to determine both R and Arg for the arguments with type std::function<int(float)> and std::function<void(float)>. This is just to determine what R and Arg are for Executor, and then the normal consideration of specializations applies, so the second object really does use the Executor<void, Arg> specialization.

But on seeing the type std::function<int()>, that doesn't match std::function<R(Arg)>, so CTAD fails. (Although you can spell an empty function parameter list (void) meaning the same as (), that's a special rule which requires a non-dependent void type, and does not apply to a template parameter which happens to have type void.)

But you can assist CTAD by writing a "deduction guide":

template <class R>
Executor(std::function<R()>) -> Executor<R, void>;

Deduction guides are used in addition to constructors of the primary template. Now an argument of type std::function<int()> void matches the deduction guide, the compiler determines that R should be int, and uses Executor<int, void>, which is defined by the second partial specialization.

See it working on coliru.

CodePudding user response:

int(void) is only usable as int() in narrow situations.

Change it like this:

template<class R, class... Args>
class Executor

and replace Arg with Args... in the body.

Nix the R, void specialization.

If you need class Executor<void, Arg> use class Executor<void, Args...>.

Your code will now compile an work.

(Why you got your error: implicitly generated deduction guides are based off of primary specialization only. std::function(testSpecialization2) is std::function<int()> which does not match any constructor of the primay specialization.)

  • Related