10

I'm struggling to understand how deduction works in the following case:

template<class Category, Category code>
struct AImpl
{ };

template<class Category, Category code>
struct AHelper
{
    using type = AImpl<Category, code>;
};

template<class Category, Category code>
using A = typename AHelper<Category, code>::type;

template<int code>
void doSomething(A<int, code> object)
{
}

Following is the test code:

A<int, 5> a1;
doSomething(a1); // This does not compile
doSomething<5>(a1); // This compiles

Why a1 is not deduced in this context?

If you modify A in the following way instead:

template<class Category, Category code>
struct A
{ };

Both work. Anyone knows why?

[edit] question linked to Mixing aliases and template specializations

Saturnu
  • 309
  • 1
  • 8

1 Answers1

9

Why a1 is not deduced in this context?

Because the template argument of doSomething appears in a non-deduced context. An alias template stands almost exactly for what it aliases. And yours is defined as follows:

template<class Category, Category code>
using A = typename AHelper<Category, code>::type;

To deduce code, the compiler will need to deduce something on the left of :: and that is a non-deduced context. Template argument deduction won't even attempt to deduce something if it appears as an argument to the left of the scope resolution operator.

It's not without cause that this is an undeduced context. Remember that templates may be specialized. I could add this:

template<Category code>
struct AHelper<int, code>
{
    using type = BImpl<code>; // My own class!
};

A compiler will need to look at all the code in the entire program and try all the types to be sure nothing nefarious is happening for it to be certain that a1 really matches as typename AHelper<Category, code>::type. That's intractable. So a mapping by meta-functions is a one way street only. You can't ask the compiler to deduce the source type (or non-type argument) from the target type.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thank you for your feedback, really interesting. I created AHelper to give more flexibility but if in this way I loose the automatic deduction it's probably not worth it. Is there any workaround that I can use, maybe modifying doSomething in some way? – Saturnu May 03 '19 at 10:58
  • 1
    @Saturnu - You could define it to accept `AImpl`. That way you get `A` to help client code more easily define objects, while the function gets what it needs always. Not perfect, but that's C++ for you. – StoryTeller - Unslander Monica May 03 '19 at 11:00
  • I've already thought about that, but then what if someone creates its own AHelper. That function won't work anymore. You would need an overload for each kind of AHelper specialization, but that is not what I was looking for. – Saturnu May 03 '19 at 11:02
  • 1
    @Saturnu - The solution is namespace. If you put your code in `namespace yourLib {}` it won't conflict with other code. So long as `yourLib` is unique enough. – StoryTeller - Unslander Monica May 03 '19 at 11:08
  • My code is already in namespace. There's something I'm missing – Saturnu May 03 '19 at 11:36
  • @Saturnu - If you have another problem, why not [ask another question](https://stackoverflow.com/questions/ask)? You can post an example of the problem there. And who knows, a solution may not look like you anticipate. – StoryTeller - Unslander Monica May 03 '19 at 11:38
  • Yep, probably I'll do that. It's just that the code will be the same eheh. Thank you – Saturnu May 03 '19 at 11:42