TransWikia.com

How to avoid ambiguous template instantiation?

Stack Overflow Asked by wintergreen_plaza on November 8, 2020

I’m trying to define a class whose behavior varies in two ways, currently based on two different template parameters.
Originally I thought that template specialization simply augmented the primary class with functionality, and so my thought was to partially specialize the two arguments separately, in the hopes that this would make available all combinations.
Below is an example of what I tried to do:

// specify the ways in which people can vary
enum class Weight { Skinny, Fat };
enum class Height { Short, Tall };

// primary class
template<Weight W, Height H>
struct Person
{
    const char* name = "name";
    void SayHello(void);
    void SayGoodbye(void);
};

// first specialize how a person says hello, based on their weight
template<Height H>
struct Person<Weight::Skinny, H>
{
    const char* goal = "ride a bike";
    void SayHello(void) {
        printf("Hello, I am skinny %s.n", name);
        printf("My goal is to %s.n", goal);
    }
};
template<Height H>
struct Person<Weight::Fat, H>
{
    const char* show = "movies";
    void SayHello(void) {
        printf("Hello, I am fat %s.n", name);
        printf("My favorite show is %s.n", show);
    }
};

// then specialize how a person says goodbye, based on their height
template<Weight W>
struct Person<W, Height::Short>
{
    void SayGoodbye(void) { printf("Goodbye, I am short %s.n", name); }
};
template<Weight W>
struct Person<W, Height::Tall>
{
    void SayGoodbye(void) { printf("Goodbye, I am tall %s.n", name); }
};

This does not work, because when I try to instantiate a Person as in

Person<Weight::Skinny, Height::Short> X;

there is no uniquely most-specialized specialization, and (from cppreference)

If more than one specialization matches, partial order rules are used to determine which specialization is more specialized. The most specialized specialization is used, if it is unique (if it is not unique, the program cannot be compiled)
The compiler says that

Clearly this is not a viable approach, but what would be a better solution?
I suspect that I am violating the single-responsibility principle somehow, but I’m not quite sure.
Any suggestions would be welcome.

Edit:

Why do you need explicit instantation / specialization in 1st place?

Ultimately, the different specializations would also manage different sets of resources; I’m not sure if that’s relevant, or a good reason.
(I recognize that what I tried to do would open the door to different specializations both declaring a variable of the same name, which I suppose is another reason this isn’t meant to work.)
The main thing is to achieve all the combinations of height/weight, and I wasn’t sure how to do that.

Edit:
Removed the solution I had added because the one from @rustyx was better.
As noted in their answer, it doesn’t allow for cross-dependencies, but in my case I think the dependencies are only one-way (Weight may care about Height, but Height does not care about Weight), so that is not an issue.

2 Answers

You can use inheritance to add degrees of freedom in functionality in "layers".

enum class Weight { Skinny, Fat };
enum class Height { Short, Tall };

struct PersonBase {
    const char* name = "name";
};

template<Weight W>
struct PersonWeight : PersonBase {
    void SayHello();
};

template<Weight W, Height H>
struct PersonHeight : PersonWeight<W> {
    void SayGoodbye();
};

template<Weight W, Height H>
struct Person : PersonHeight<W, H> {
};

template<>
struct PersonWeight<Weight::Skinny> : PersonBase {
    const char* goal = "ride a bike";
    void SayHello() {
        printf("Hello, I am skinny %s.n", name);
        printf("My goal is to %s.n", goal);
    }
};
template<>
struct PersonWeight<Weight::Fat> : PersonBase {
    const char* show = "movies";
    void SayHello() {
        printf("Hello, I am fat %s.n", name);
        printf("My favorite show is %s.n", show);
    }
};

template<Weight W>
struct PersonHeight<W, Height::Short> : PersonWeight<W> {
    void SayGoodbye() { printf("Goodbye, I am short %s.n", name); }
};

template<Weight W>
struct PersonHeight<W, Height::Tall> : PersonWeight<W> {
    void SayGoodbye() { printf("Goodbye, I am tall %s.n", name); }
};

This won't work if there's a cross-dependency between the layers though. In that case if constexpr or SFINAE might help.

Correct answer by rustyx on November 8, 2020

You can simply use an if condition in your functions to decide what message to print, like this:

template<Weight W, Height H>
struct Person
{
    void SayHello(void) {
      if (W == Weight::Skinny)
        std::cout << "Hello, I am skinny.n";
      else  
        std::cout << "Hello, I am fat.n";
    }
    
    void SayGoodbye(void) {
      if (H == Height::Short)
        std::cout << "Goodbye, I am short.n";
      else  
        std::cout << "Goodbye, I am tall.n";
    }
};

Here's a demo.

Answered by cigien on November 8, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP