Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template parameters of function type with auto return type arguments of previous template parameter types

I have a template with two parameters: the first is a type, and the second is a function pointer with an argument whose type is the first template parameter. This MCVE works:

void returnsVoid(int x) { }

template <typename T, void (*Func)(T)>
struct foo { void bar(T t) { Func(t); } };

int main(int, char *[]) {
    foo<int, returnsVoid> a; // ok
}

However, when I change the return type of the second template parameter to auto (as explained in this related question), I get an error:

void returnsVoid(int x) { }

template <typename T, auto (*Func)(T)>
struct foo {
    void bar(T t) { Func(t); }
};

int main(int, char *[]) {
    foo<int, returnsVoid> a; // error: unable to deduce ‘auto (*)(T)’ from ‘returnsVoid’
                             // note:   mismatched types ‘T’ and ‘int’
}

Why does this no longer work with an auto return type?

I'm using g++ 9.3.0 on an Ubuntu Linux machine. A similar error occurs in clang 10.

like image 267
TypeIA Avatar asked Jan 27 '21 09:01

TypeIA


People also ask

What are template arguments enlist types of template arguments?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

Which is correct example of template parameters?

For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.

Can we pass Nontype parameters to templates?

Template classes and functions can make use of another kind of template parameter known as a non-type parameter. A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument.

How do you use template arguments in C++?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)


2 Answers

This is gcc's bug. (Bug 83417)

It seems gcc failing deducing the type of auto when using the 1st template parameter T as function parameter; you can use std::type_identity (since C++20) to exclude it from participating in template argument deduction.

// workaround for gcc
template <typename T, auto (*Func)(std::type_identity_t<T>)>
struct foo {
    void bar(T t) { Func(t); }
};

LIVE

BTW: Clang 10 seems working well.

like image 61
songyuanyao Avatar answered Nov 14 '22 23:11

songyuanyao


All standard references below refer, unless noted otherwise, to N4861 (March 2020 post-Prague working draft/C++20 DIS).


TL;DR;

Jump to the A workaround to mitigate the GCC bug section in the bottom of this post for a workaround, accepted by GCC and Clanng, which still relies of deduction of dependent types to avoid a client having to specify a second template argument for the actual type of the function parameter associated with the "main" template argument; namely the function pointer used associated non-type template parameter.

Standardese

As per [temp.deduct.type]/13:

When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value. [ Example:

template<long n> struct A { };

template<typename T> struct C;
template<typename T, T n> struct C<A<n>> {
  using Q = T;
};

using R = long;
using R = C<A<2>>::Q;  // OK; T was deduced as long from the
                       // template argument value in the type A<2>

end example ]

any dependent types in the declaration of a non-type template parameter that undergoes type deduction (from an expression) shall also be deduced from the associated argument for the non-type template parameter. It is also essential that [temp.deduct.type]/5, covering the non-deduced contexts, does not apply for general uses of dependent types within non-type template parameters that are function pointers; meaning in the OP's example, T is a dependent type and is thus deduced from the value of the argument to the non-type (function pointer) template parameter.

A common problem when a given, say, type template parameter is deduced from more than one source (e.g. as in the example of OP), is that deduction yields different types; e.g. as showing in the following blog post:

template<typename T>
struct Foo { T t; };

template<typename T>
void addToFoo(Foo<T>& foo, T val) { foo.t += val; }

int main() {
    Foo<long> f{42};
    addToFoo(f, 13); // error: no matching function for call to 'addToFoo'
                     // note: candidate template ignored: deduced conflicting
                     //       types for parameter T (long vs. int).
    return 0;
}

As has been shown in @songyuanyao: answer (and as is shown also in the blog post), a type identity transformation trait can be used to intentionally place a given template parameter in a non-deduced context for cases where several deduction sources yields conflicting results.

However, the root cause of OP:s failure is not conflicting deduction results (this is a red herring), but rather GCC:s failure to correctly deduce template parameter from when another template parameter is deduced, where the former is present as a dependent type.

Thus, if we go back to [temp.deduct.type]/13, for the following class template and subsequent partial specialization:

// #1
template <auto>
struct A { static void dispatch() = delete; };

// #2
template <typename T, void (*fun)(T)>
struct A<fun> {
    static void dispatch() {
        T t{};
        fun(t);
    }
};

the following:

A<f>::dispatch(); // #3

is well-formed if f is a function with a single argument (of a type that is default-constructible) which returns void, e.g.

void f(int) { std::cout << "void f(int)\n"; }
// -> #3 is well-formed

as this will match the partial specialization at #2, deducing T to int and the non-type template parameter (which decides the specialization blueprinted by the primary template), which is dependent on T in this partial specialization, to void(*)(int).

On the other hand, #3 is ill-formed if f does not return void, as the partial specialization at #2 is no longer viable.

void f(int) { std::cout << "int f(int)\n"; }
// -> #3 is ill-formed

The key here is that:

  • the partial specialization at #2 applies only for template arguments to A which match the second (non-type) template parameter of the specialization, and
  • the first template parameter of the specialization,T, is deduced from the deduction of the second (non-type) template parameter, as T is a dependent type in the declaration of the second template parameter.

Both GCC and Clang works as expected for the two cases above.

Now, if we consider the similar example as to that of #1 and #2 above:

// #4
template <auto>
struct B { static void dispatch() = delete; };

// #5
template <typename T, auto (*fun)(T)>
struct B<fun> {
    static void dispatch() {
        T t{};
        fun(t);
    }
};

// ... elsewere
// #6
B<f>::dispatch();

the same argument as above applies:

  • if template argument f refers to a function (now with less restrictions) that has a single argument (of a type that is default-constructible), then the partial specialization at #5 is viable, and its second non-type template parameter, which contains its first type template parameter as a dependent type, shall be used to deduce the latter.

The significant difference in this case is that the type of the non-type template parameter itself undergoes [temp.arg.nontype]/1:

If the type T of a template-parameter contains a placeholder type ([dcl.spec.auto]) or a placeholder for a deduced class type ([dcl.type.class.deduct]), the type of the parameter is the type deduced for the variable x in the invented declaration

T x = template-argument ;

If a deduced parameter type is not permitted for a template-parameter declaration ([temp.param]), the program is ill-formed.

but [temp.deduct.type]/13 still applies the same, and we may not that [temp.deduct.type]/13 was actually added as part of P0127R2 (Declaring non-type template parameters with auto) which introduced placeholder types for non-type template parameters for C++17.

Thus, the core issue is that GCC fails to perform dependent type deduction (as specified in [temp.deduct.type]/13) when the non-type template parameter in whose declaration the dependent type (to be deduced) is present is a function pointer (or, as shown in the linked to GCC bug report, as pointer to member) AND the non-type template parameter is declared with a placeholder type (auto).

@songyuanyao: answer shows a workaround to this bug, applied to OP's example, simply making the dependent type non-dependent, as the associated template parameter can be deduced from elsewhere (namely from the first template argument in OP's example). This would not work for the examples above, where we rely solely on the dependent type deduction in the deduction of the non-type template parameter to find the type of the type template parameter (which is the dependent type in the former).

For an actual client API, requiring the client to explicitly specify the type of the argument to the function which is provided as another argument, when the former is entirely deducible from the latter, is arguably redundant design, and opens up for client confusion when providing conflicting arguments for these two template parameters. Thus, we'd arguably like to fall back on the partial specialization technique shown above, but as shown in these answers, GCC fails us in this regard in case we'd like the client to not be restricted to a specific return type.

A workaround to mitigate the GCC bug

We can work our way around this, however, by using the same approach as above, still relying on [temp.deduct.type]/13, but by using an additional type template parameter (for the return type) in the partial specialization:

#include <iostream>

template <auto>
struct C { static void dispatch() = delete; };

template <typename T, typename Return, Return (*fun)(T)>
struct C<fun> {
    static void dispatch() {
        T t{};
        fun(t);
    }
};

void f(int) { std::cout << "void f(int)\n"; }
int g(int) { std::cout << "int f(int)\n"; return 0; }

int main() {
    C<f>::dispatch();
    C<g>::dispatch();
}

The client will not need to worry about the additional template parameters of the partial specialization, as they entirely deducible via the third non-type template parameter of the specialization, in whose declaration they are dependent. This final example is accepted by both GCC and Clang.

like image 25
dfrib Avatar answered Nov 14 '22 21:11

dfrib