Workaround for Template Argument Deduction in Non-Deduced Context

Workaround for template argument deduction in non-deduced context

You can move the operator into the inner class body and put friend before it. Then replace the parameter type by just inner.

Another technique is to derive inner from a CRTP base parameterized by inner. Then make the parameter type the CRTP class and cast the parameter reference to the derived inner class, the type of which is given by the template argument you deduce.

Why does an optional argument in a template constructor for enable_if help the compiler to deduce the template parameter?

Template argument deduction does not work this way.

Suppose you have a template and a function using a type alias of that template:

template <typename T>
struct foo;

template <typename S>
void bar(foo<S>::type x) {}

When you call the function, eg foo(1) then the compiler will not try all instantiations of foo to see if any has a type that matches the type of 1. And it cannot do that because foo::type is not necessarily unambiguous. It could be that different instantiations have the same foo<T>::type:

template <>
struct foo<int> { using type = int; };

template <>
struct foo<double> { using type = int; };

Instead of even attempting this route and potentially resulting in ambiguity, foo<S>::type x is a nondeduced context. For details see What is a nondeduced context?.

Template argument deduction from inherited type

Is there a way to get the compiler to deduce the template parameter in this case?

No. Not in C++14 (or even C++20).

And what would you call this behaviour?

Standard compliant. To be specific, this paragraph applies:

[temp.deduct.call]

4 In general, the deduction process attempts to find template
argument values that will make the deduced A identical to A (after
the type A is transformed as described above). However, there are
three cases that allow a difference:

  • If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the
    transformed A.
  • The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification
    conversion ([conv.qual]).
  • If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A.
    Likewise, if P is a pointer to a class of the form
    simple-template-id, the transformed A can be a pointer to a derived class pointed to by the deduced A.

This is an exhaustive list of cases where a template argument can be validly deduced from a function argument even if it doesn't match the pattern of the function parameter exactly. The first and second bullets deal with things like

template<class A1> void func(A1&){}
template<class A2> void func(A2*){}

int main() {
const int i = 1;
func(i); // A1 = const int
func(&i); // A2 = const int
}

The third bullet is the one that is closest to our case. A class derived from a template specialization can be used to deduce a template parameter pertaining to its base. Why doesn't it work in your case? Because the function template parameter is unique_ptr<a<T>> and the argument you call it with is unique_ptr<b>. The unique_ptr specializations are not themselves related by inheritance. So they don't match the bullet, and deduction fails.

But it doesn't mean that a wrapper like unique_ptr prevents template argument deduction entirely. For instance:

template <typename> struct A {};
struct B : A<int> {};

template<typename> struct wrapper{};
template<> struct wrapper<B> : wrapper<A<int>> {};

template<typename T>
void do_smth(wrapper<A<T>>) {}

int main() {
do_smth(wrapper<B>{});
}

In this case, wrapper<B> derives from wrapper<A<int>>. So the third bullet is applicable. And by the complex (and recursive) process of template argument deduction, it allows B to match A<T> and deduce T = int.

TL;DR: unique_ptr<T> specializations cannot replicate the behavior of raw pointers. They don't inherit from the specializations of unique_ptr over T's bases. Maybe if reflection ever comes to C++, we'll be able to meta-program a smart pointer that does behave that way.

Why is the template argument deduction not working here?

Just as first note, typename name is used when you mention a dependent name. So you don't need it here.


template <class T>
struct S
{
typedef T& type;
};

Regarding the template instantiation, the problem is that typename S<A>::type characterizes a nondeduced context for A. When a template parameter is used only in a nondeduced context (the case for A in your functions) it's not taken into consideration for template argument deduction. The details are at section 14.8.2.4 of the C++ Standard (2003).

To make your call work, you need to explicitly specify the type:


temp<char>(c);

Template parameter is ambiguous: could not deduce template argument

Why it fails

Currently, the template parameter Value is deduced in two different places in the call to Apply: from the pointer to member function argument and from the last argument. From &Foo::MyFunc, Value is deduced as int const&. From f.GetValue(), Value is deduced as int. This is because reference and top-level cv-qualifiers are dropped for template deduction. Since these two deductions for the parameter Value differ, deduction fails - which removes Apply() from the overload set, and as a result we have no viable overload.

How to fix it

The problem is that Value is deduced in two different places, so let's just prevent that from happening. One way is to wrap one of the uses in a non-deduced context:

template <class T> struct non_deduced { using type = T; };
template <class T> using non_deduced_t = typename non_deduced<T>::type;

template<class T, class Value>
void Apply(void (T::*cb)(Value), T* obj, non_deduced_t<Value> v)
{
(obj->*cb)(v);
}

The last argument, v, is of type non_deduced_t<Value> which, as the name suggests, is a non-deduced context. So during the template deduction process, Value is deduced as int const& from the pointer to member function (as before) and now we simply plug that into the type for v.

Alternatively, you could choose to deduce cb as its own template parameter. At which point Apply() just reduces to std::invoke().

How can I create deduction guides for template aliases in C++20?

To do what you want, C++ would have to be able to invert a Turing-complete subprogram.

Turing-complete programs are not only not-invertible, it isn't possible to determine if a given Turing-complete program is invertible or not. You can define sublanguages where they are all invertible, but those sublanguages lack Turing-complete power.

Deducing the Bar alias argument:

template <typename T>
using Bar = Foo<1, T>;

requires inverting the 2nd template argument alias<F> to find F. When alias is a trivial template alias, this is possible, permitted, and required by the C++ standard.

When alias2 evaluates is to a foo<F>::type, such a construct is capable of turing-complete computation. Rather than have compilers try to invert such a computation, the C++ standard uniformly says "don't try". It uses "dependent type" usually to block such an inversion attempt.

In your second case, Bar is a trivial alias of Foo. Foo has a deduction guide. That deduction guide tells how to get from F to T, so the compiler doesn't have to invert any potentially Turing-complete programs in order to determine T.

The C++ language has a bunch of wording to permit template aliases that are just renaming of parameters or the like to act as if they were the original type. Originally even a toy alias would block a bunch of this kind of deduction; but this was found to be a bad plan. So they added text to the standard to describe what kind of template aliases where "trivial" like that, and modified the wording of deduction rules so that they would be treated as transparent.


In order to invert an arbitrary Turing-complete program (in fact, almost any structurally non-trivial type transformation) during type deduction, you must explicitly give the inversion.

Once you have accepted that, it becomes battle with syntax, not a conceptual one.

I'm unaware of the current status of user-defined template deduction guides of alias templates. Last I heard it wasn't supported, but I haven't checked recently.

Template argument deduction failure

The issue is that std::function<Output(Output,Input)> cannot properly deduce its template arguments, because std::plus<int> is not a std::function.

Type-deduction only works if the template types can match up with the arguments being passed; it doesn't perform transformations that would be done through conversions/constructions; but rather takes the type in totality.

In this case, std::plus<int> is not a std::function<int(int,int)>, it is convertible to a std::function<int(int,int)>. This is what is causing the deduction to fail.

The alternatives are to either break contributions to the type-deduction with an alias, to explicitly pass a std::function type, or to accept the function as a different template argument (which may actually be more efficient)

1. Break contribution to the type-deduction with an alias

If you make an alias of the type, so that the types don't contribute to the deduction, then it can leave the first and third arguments to determine the types of the std::function

(off the top of my head; untested)

template<typename T>
struct nodeduce{ using type = T; };

template<typename T>
using nodeduce_t = typename nodeduce<T>::type;

template<class Input, class Output>
Output fold_left(Output zero, std::function<nodeduce_t<Output>(nodeduce_t<Output>,nodeduce_t<Input>)> op, std::valarray<Input> data)
{
....
}

It's a bit of an ugly approach, but it will disable std::function from contributing to the deduction because the types are transformed through nodeduce_t.

2. Pass as std::function

This is the simplest approach; your calling code would become:

 fold_left(0, std::function<int(int,int)>(std::plus<int>{}), data);

Admittedly, it's not pretty -- but it would resolve the deduction because std::function<int(int,int)> can directly match the input.

3. Pass as template argument

This actually has some additional performance benefits; if you accept the argument as a template type, then it also allows your function to be inlined better by the compiler (something that std::function has issues with)

template<class Input, typename Fn, class Output>
Output fold_left(Output zero, Fn&& op, std::valarray<Input> data)
{
for (auto const& item : data) {
zero = op(zero, item);
}
return zero;
}

All of these are possible alternative that would accomplish the same task.
It's also worth mentioning that compile-time validation can be done using SFINAE with things like enable_if or decltype evaluations:

template<class Input, typename Fn, class Output, typename = decltype( std::declval<Output> = std::declval<Fn>( std::declval<Output>, std::declval<Input> ), void())>
Output fold_left( ... ){ ... }

The decltype(...) syntax ensures the expression is well-formed, and if it is, then it allows this function to be used for overload resolution.

Edit: Updated the decltype evaluation to test Output and Input types. With C++17, you can also use std::is_convertible_v<std::invoke_result_t<F&, Output, Input>,Output> in an enable_if, as suggested by Barry in a different answer.

Could not deduce template argument (vector, std::function)

Template argument deduction doesn't consider implicit conversion.

Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

Then type deduction of the template parameter Value on the 2nd function argument f fails because the implicit conversion from lambda to std::function are not considered.

As you showed you can specify template arguments explicitly (to bypass the template argument deduction). Or you can exclude the 2nd argument from deduction by using std::type_identity to declare it as non deduced context.

The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:

e.g.

template<class Value, class Allocator>
void foo(const std::vector<Value, Allocator>& v, const std::function<void(const std::type_identity_t<Value>&)>& f)
{
}

LIVE

PS: std::type_identity is supported from C++20; if the compiler you're using doesn't support it it's not hard to make one.

SFINAE works with deduction but fails with substitution

Deduction of the function called in a function call expression is performed in two steps:

  1. Determination of the set of viable functions;
  2. Determination of the best viable function.

The set of viable function can only contain function declaration and template function specialization declaration.

So when a call expression (test(a,b) or test<A>(a,b)) names a template function, it is necessary to determine all template arguments: this is called template argument deduction. This is performed in three steps [temp.deduct]:

  1. Subsitution of explicitly provided template arguments (in names<A>(x,y) A is explicitly provided);(substitution means that in the function template delcaration, the template parameter are replaced by their argument)
  2. Deduction of template arguments that are not provided;
  3. Substitution of deduced template argument.

Call expression test(a,b)

  1. There are no explictly provided template argument.
  2. T is deduced to A for the first template function, deduction fails for the second template function [temp.deduct.type]/8. So the second template function will not participate to overload resolution
  3. A is subsituted in the declaration of the first template function. The subsitution succeeds.

So there is only one overload in the set and it is selected by overload resolution.

Call expression test<A>(a,b)

(Edit after the pertinent remarks of @T.C. and @geza)

  1. The template argument is provided: A and it is substituted in the declaration of the two template functions. This substitution only involves the instantiation of the declaration of the function template specialization. So it is fine for the two template
  2. No deduction of template argument
  3. No substitution of deduced template argument.

So the two template specializations, test<A>(A,A) and test<A>(Wrapper<A>,Wrapper<A>), participate in overload resolution. First the compiler must determine which function are viable. To do that the compiler needs to find an implicit conversion sequence that converts the function argument to the function parameter type [over.match.viable]/4:

Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter of F.

For the second overload, in order to find a conversion to Wrapper<A> the compiler needs the definition of this class. So it (implicitly) instantiates it. This is this instantiation that causes the observed error generated by compilers.



Related Topics



Leave a reply



Submit