Search code examples
c++c++14constantstemplate-argument-deduction

Template argument deduction failure due to inconsistent constness


Consider the following (which doesn't compile, but we'll fix that in a moment):

void foo(const int *n) { }

template <typename ...Args>
void bar(void (*func)(Args...), Args... args) { func(args...); }

int main(int argc, char *argv[])
{
    int n = 42;
    bar(foo, &n);
}

The template function bar() takes a function pointer to call and a parameter pack of arguments to pass to it. gcc 7.4.0 diagnoses the following error:

test.cpp:6:6: note:   template argument deduction/substitution failed:
test.cpp:11:16: note:   inconsistent parameter pack deduction with ‘const int*’ and ‘int*’

So clearly, the type deduction rules are not relaxed enough to allow const T* to be deduced when both const T* and T* are observed. OK, fine. This is easy enough to fix with a cast:

bar(foo, static_cast<const int *>(&n));

But this is ugly. C++17 has std::as_const() which makes it a little less ugly (&std::as_const(n)) but in my current project I'm limited to C++14, sadface.

Q: Is there a way to rearrange this code so that type deduction succeeds without explicitly specifying the template parameters for bar() and without casting to resolve the ambiguous constness? Thinking outside the box is allowed, as long as I can pass a function pointer and its arguments to a template function!


Solution

  • You can separate the deduction for the function pointer and the parameters:

    void foo(const int *n) {}
    
    template <typename... FArgs, typename... Args>
    void bar(void (*func)(FArgs...), Args&&... args) {
        func(std::forward<Args>(args)...);
    }
    
    int main(int argc, char *argv[]) {
        int n = 42;
        bar(foo, &n);
    }
    

    But at that point I wonder why the function pointer need to be decompose. Why not accept any callable?

    void foo(const int *n) {}
    
    template <typename F, typename... Args>
    void bar(F func, Args&&... args) {
        func(std::forward<Args>(args)...);
    }
    
    int main(int argc, char *argv[]) {
        int n = 42;
        bar(foo, static_cast<const int *>(&n));
        bar([](int const*){}, &n);
    }
    

    Also, remember that C++17 offers std::invoke:

    std::invoke(foo, &n);