Mixing aliases and template specializations
Asked Answered
A

1

2

I'm trying to find the best method to have a kind of "object" that can be either specialized or "linked" to another type.

For instance you cannot specialize a class to make it become a simple int, and you cannot use the keyword using to specialize classes.

My solution is the following:

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)
  {
  }

template<>
struct AImpl<int, 5>
  { 
  double a; 
  };

template<>
struct AImpl<int, 6>
  { 
  int b; 
  double c;
  };

template<>
struct AHelper<int, 7>
  {
  using type = int;
  };

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

template<int code>
void doSomethingAlternative(Alternative<int, code> object)
  {
  }

This works but you need to specify the code parameter in doSomething, and I would like to avoid that.

For instance:

A<int,7> a7; // This is equivalent to int
a7 = 4;
A<int, 5> a5; // This is equivalent to AImpl<int,5>
a5.a = 33.22;
doSomething(a5); // This does not compile
doSomething<5>(a5); // This compiles but is bulky
Alternative<int,0> alt0;
doSomethingAlternative(alt0); // This compiles and is not bulky
                              // but you're forced to use class
                              // specializations only

Is there a way to achieve what I want? It's ok to change both doSomething or the A implementation.

Armbruster answered 3/5, 2019 at 11:52 Comment(6)
There are no template specializations in your code. It is not clear to me what the overall goal here is beyond the exact implementation you show. I assume you want to be able to swap out AImpl later on, but it's not clear why you don't do that directly via using A = AImpl1<...>.Barbet
In other words, what is the point of AHelper? It doesn't seem necessary in the code you show, so could you extend the example to include a purpose for the AHelper part (which seems to be the core of the problem)?Barbet
@MaxLanghof you're right, code changed. I hope it's more clear nowArmbruster
I've added some other code to show you the alternative.Armbruster
You omitted the implementation of doSomething for obvious reasons, but what code is it that works exactly the same for both fundamental types and class types? And how is Category needed in such a function for a plain integer?Hoggish
@StoryTeller there are many possible usages, but the point is that this approach can be used not only with fundamental types but you can also link to other classes. For instance you can make it link to int, std::vector, whatever you wantArmbruster
H
2

If you are trying to customize the behavior of doSomething based on the type it is called with, you cannot have the compiler deducing stuff from AHelpr::type (as previously answered). But you can tell it what it needs to know by providing a customization point in the form of traits. For instance:

template<typename T>
void doSomething(T& object)
{
    auto code = SmthTriats<T>::code;
}

This is highly extendable given the ability to specialize SmthTriats:

template<typename> struct SmthTriats;

template<typename Category, Category code_>
struct SmthTriats<AImpl<Category, code_>> {
    static constexpr auto code = code_;
};

template<>
struct SmthTriats<int> {
    static constexpr auto code = 7;
};

A traits class allows for customization outside your module too. Client code need only specialize SmthTriats with their own type, and so long as they respect the contract you laid out with your trait, your code will work for them.

Hoggish answered 3/5, 2019 at 12:32 Comment(13)
Interesting, would this work in C++11 too? I think however it's something different that what I'm looking for thoughArmbruster
@Armbruster - Of course. The idea of traits is almost as old as C++ templates. The standard library makes heavy use of them itself. They are a convenient way to provide interesting properties about a type (or family of types, in the case of partial specializations).Hoggish
If you are looking to make your original attempt work, I urge you not to get fixated on a solution. Give this a try in your code base. This technique is industry standard for a good reason.Hoggish
I totally agree with you about not getting fixated on a solution. Yep, I know traits, you can consider AHelper a kind of traits too. I'm going to check well your solution and give you a feedback asap.Armbruster
@Armbruster - Here's another way to think about it. AHelper is indeed a trait (a map to a type). The compiler can't assume the mapping is two way (as mentioned). You need to tell it how to map in reverse via another trait.Hoggish
Yep interesting explanation. The fact is that the solution you've posted doesn't seem to fit well for my particular case.Armbruster
Ps were you wrote T, did you mean Category right? The fact is that I'm not trying to get types / values from a type, I need the type itselfArmbruster
@Armbruster - In doSomething? No, I meant T. The type of the argument is open ended (otherwise, how would int be supported?). The trait is what analyzes T and tells us properties about it.Hoggish
I think that then you need to add a class Category over there, just to make it compileArmbruster
@Armbruster - I see what you meant. Yes, fixed the trait.Hoggish
Ok, now I've understood your solution. It doesn't apply for what I'm doing but it's quite cool, I think I can use it in some way to improve the solution I posted. The fact is that I don't need to get values / types from a type , I need to operate on the type itself. Serialization in my specific case. Probably I just need to pass another traits like the one you wrote in one of the above functionsArmbruster
I've tried to use your approach, creating a kind of reverse traits helper. It works only in certain cases. It is not possible to map same type to different Category / code combinations. It's an interesting idea thoughArmbruster
best and only answer so far, so you totally deserve the accepted answer :) Only problem is as I said that you cannot use a type more than once (e.g. int for two different codes)Armbruster

© 2022 - 2024 — McMap. All rights reserved.