C++ Template Parameter Type Inference

c++ template parameter type inference

UPDATE

c++17 introduced "P0127R2 Declaring non-type template parameters with auto", allowing to declare a non-type template parameter(s) with auto as a placeholder for the actual type:

template <auto P> struct Ptr {};

That is, P is a non-type template parameter. Its type can be inferred with decltype(P).

auto in a template parameter list is subject to well-known deduction and partial ordering rules. In your case, the type can be constrained to accept pointers only:

template <auto* P> struct Ptr {};

Note that the syntax utilizing auto is sufficient even for more detailed inspection, e.g.:

template <typename F>
struct FunctionBase;

template <typename R, typename... Args>
struct FunctionBase<R(*)(Args...)> {};

template <auto F>
struct Function : FunctionBase<decltype(F)> {};

It's also possible to use the inferred type as a contraint for other template parameters:

template <auto I, decltype(I)... Is>
struct List {};

Old answer

Since you are asking about a pure class template-based solution without the help of macro definitions then the answer is simple: as for now (Dec 2014, c++14) it is not possible.

This issue has been already identified by the WG21 C++ Standard Committee as a need and there are several proposals to let templates automatically infer the type of non-type template arguments.

The closest is N3601 Implicit template parameters:

Implicit template parameters


The purpose of this example is to eliminate the need for the redundant template<typename T, T t> idiom. This idiom is widely used, with over 100k hits on Google.

The goal is to be able to replace a template declaration like template<typename T, T t> struct C; with another declaration so that we can instantatiate the template like C<&X::f> instead of having to say C<decltype(&X::f), &X::f>.

The basic idea is to be able to say template<using typename T, T t> struct C {/* ... */}; to indicate that T should be deduced. To describe in more detail, we consider some extended examples of template classes and functions.

[...]

The key idea is that passing the type of the second template parameter is redundant information because it can be inferred using ordinary type deduction from the second type parameter. With that in mind, we propose that prefacing a template parameter with using indicates that it should not be passed explicitly as a template argument but instead will be deduced from subsequent non-type template arguments. This immediately allows us to improve the usability of describe_field as follows.

template<using typename T, T t> struct describe_field { /* ... */ };
/* ... */
cout << describe_field<&A::f>::name; // OK. T is void(A::*)(int)
cout << describe_field<&A::g>::arity; // OK. T is double(A::*)(size_t)

A similar proposal is the one included in N3405 Template Tidbits:

T for two


The motivating example is a putative reflection type trait giving properties of a class member.

struct A {
void f(int i);
double g(size_t s);
};
/* ... */
cout << describe<&A::f>::name; // Prints "f"
cout << describe<&A::g>::arity; // prints 1

The question is "what should the declaration of describe look like?" Since it takes a non-type template parameter, we need to specify the type of the parameter using the familiar (100k hits on Google) “template<class T, T t>” idiom

template<typename T, T t> struct describe;

[...]

Our key idea is that passing the type of the second template parameter is (nearly always) redundant information because it can be inferred using ordinary type deduction from the second type parameter. With that in mind, we propose allowing describe to be declared as follows.

template<typename T t> struct describe;
/* ... */
cout << describe<&A::f>::name; // OK. T is void(A::*)(int)
cout << describe<&A::g>::arity; // OK. T is double(A::*)(size_t)

The current status of both proposals can be tracked under EWG issue 9.

There are some other discussions proposing alternative syntax with auto:

template <auto T> struct describe;

How to infer a function type parameter in a template function with a lambda passed as argument?

The actual problem is that a lambda function has it own type, that cannot be reduced to R(&)(T). Because of that, C<T> is an incomplete type as correctly outlined by the compiler.


As long as you use non-capturing lambdas, you can rely on the fact that they decay to function pointers and do this:

auto p3 = makeC(*(+[](int a){return a;}));

Or this:

template<typename T>
auto makeC(T&& fun) -> C<decltype(*(+std::forward<T>(fun)))> {
return C<decltype(*(+std::forward<T>(fun)))>(std::forward<T>(fun));
}

Another possible solution that works with capturing lambdas is this:

#include <utility>
#include <string>

template<typename T>
class C: T {
template<typename F>
C(F&& fun): T{std::forward<F>(fun)} {}
};

template<typename R, typename T>
class C<R(&)(T)> {
public:
template<typename F>
C(F&& fun) {}
};

template<typename T>
C<T> makeC(T&& fun) {
return C<T>(std::forward<T>(fun));
}

int foo(int a){return a;}

int main() {
auto p1 = makeC(foo);
auto p2 = C<int(&)(int)>([](int a){return a;});
auto p3 = makeC([](int a){return a;});
}

This way, when dealing with a lambda, C actually inherits from it and privately contains its operator().

Template type inference using std::views

This has nothing to do with views. You can reduce the problem to:

template <typename T>
int length(T const& x) { return x.length(); }

template <typename F>
void do_something(F&& f) {
// in theory use f to call something
}

void stuff() {
do_something(length); // error
}

C++ doesn't really do type inference. When you have do_something(length), we need to pick which length we're talking about right there. And we can't do that, so it's an error. There's no way for do_something to say "I want the instantiation of the function template that will be called with a std::string - it's entirely up to the caller to give do_something the right thing.

The same is true in the original example. length<E> is a concrete function. length is not something that you can just pass in.

The typical approach is to delay instantiation by wrapping your function template in a lambda:

void stuff() {
do_something([](auto const& e) { return length(e); }); // ok
}

Now, this works - because a lambda is an expression that has a type that can be deduced by do_something, while just length is not. And we don't have to manually provide the template parameter, which is error prone.

We can generalize this with a macro:

#define FWD(arg) static_cast<decltype(arg)&&>(arg)
#define LIFT(name) [&](auto&&... args) -> decltype(name(FWD(args)...)) { return name(FWD(args)...); }

void stuff() {
do_something(LIFT(length));
}

Which avoids some extra typing and probably makes the intent a little clearer.

Template function default parameter and type inference

Your examples don't work because template argument deduction fails for foo(). With C++11 you're allowed to specify default template arguments for function templates, so you change the definition to

template<typename T = void*> void foo(T par = nullptr) {return;}

With C++03 I don't know of any way other than to explicitly specify the template argument.

The reason the template argument is not deduced from the default argument is because the standard states that is a non-deduced context.

From N3691, §14.8.2.5/5

The non-deduced contexts are:

...

— A template parameter used in the parameter type of a
function parameter that has a default argument that is being used in
the call for which argument deduction is being done.

When a compiler can infer a template parameter?

Template parameters can be inferred for function templates when the parameter type can be deduced from the template parameters

So it can be inferred here:

template <typename T>
void f(T t);

template <typename T>
void f(std::vector<T> v);

but not here:

template <typename T>
T f() {
return T();
}

And not in class templates.

So the usual solution to your problem is to create a wrapper function, similar to the standard library function std::make_pair:

  template <class T>
class MyClass {
public:
MyClass(T t) {}
void print(){
std::cout<<"try MyClass"<<std::endl;
}
};

template <typename T>
MyClass<T> MakeMyClass(T t) { return MyClass<T>(t); }

and then call auto a = MakeMyClass(5); to instantiate the class.



Related Topics



Leave a reply



Submit