Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a parameter pack in function template be followed by another parameter which depends on the return type?

I have a function where a template type parameter follows a parameter pack. It looks like this:

template<typename...Args, typename T>
T* default_factory_func()
{
    return new T;
}

Visual C++ compiler rejects it with an error C3547: template parameter 'T' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'default_factory_func'.

However, I tried various versions of GCC (starting with 4.4.7) and clang (starting with 3.1) available on Compiler Explorer, and all of them compile such code just fine.

// this code is just a minimal example condensed
// from a much more complex codebase
template<typename T>
T* construct(T* (*factory_func)())
{
    return factory_func();
}

template<typename...Args, typename T>
T* default_factory_func() // C3547 on this line
{
    return new T(Args()...);
}

struct some_class {
    some_class(int, int, int) {}
};

int main()
{
    construct<some_class>(
        default_factory_func<int,int,int>
    );
}

Is this some quirk of MSVC or is it not allowed by the standard?

like image 945
Hedede Avatar asked Sep 07 '21 17:09

Hedede


1 Answers

I think the standard is confused here (probably needs an issue if one doesn't already exist).

  • The definition of default_factory_func is ill-formed per [temp.param]

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list ([dcl.fct]) of the function template or has a default argument

  • At the same time, the types of default_factory_func can be (arguably) deduced per [temp.deduct.funcaddr] because you are attempting to match a target type of some_class*(*)(void) when you pass &default_factory_func<int,int,int>

Template arguments can be deduced from the type specified when taking the address of an overload set. If there is a target, the function template's function type and the target type are used as the types of P and A, and the deduction is done as described in [temp.deduct.type]. Otherwise, deduction is performed with empty sets of types P and A

(Thanks to n.m. for pointing this second one out in their now-deleted-answer)

I think the safest bet would be to avoid running afoul of the first rule by reordering your template arguments:

template<class T, typename...Args>
T* default_factory_func()
{
    return new T(Args()...);
}

And then explicitly casting your function pointer to resolve the overload:

auto foo = construct(
    static_cast<some_class*(*)()>(default_factory_func<some_class, int, int, int>)
);

Live Code

(Compiles on gcc/clang/and msvc latest)

like image 179
AndyG Avatar answered Nov 10 '22 19:11

AndyG