如何避免不明确的模板实例化?

How to avoid ambiguous template instantiation?

我正在尝试定义一个 class,其行为有两种变化,目前基于两种不同的模板参数。 最初我认为模板特化只是增加了主要 class 的功能,所以我的想法是分别部分特化这两个参数,希望这将使所有组合可用。 下面是我尝试做的一个例子:

// 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); }
};

这不起作用,因为当我尝试实例化一个 Person 时,如

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

没有唯一的最专业化,并且(来自 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

显然这不是一个可行的方法,但更好的解决方案是什么? 我怀疑我以某种方式违反了单一职责原则,但我不太确定。 欢迎任何建议。

编辑:

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

最终,不同的专业也将管理不同的资源集;我不确定这是否相关,或者是一个充分的理由。 (我认识到我尝试做的事情会打开通向不同专业化的大门,两者都声明一个同名变量,我想这是这不起作用的另一个原因。) 最主要的是实现height/weight的所有组合,我不知道该怎么做。

编辑: 删除了我添加的解决方案,因为来自@rustyx 的解决方案更好。 正如他们的回答中所指出的,它不允许交叉依赖,但就我而言,我认为依赖只是单向的(Weight 可能关心 Height,但 Height不关心 Weight),所以这不是问题。

您可以简单地在函数中使用 if 条件来决定要打印的消息,如下所示:

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";
    }
};

这是一个demo

您可以使用继承来增加“层”功能的自由度。

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

如果层与层之间有 cross-dependency,这将不起作用。在那种情况下 if constexpr 或 SFINAE 可能会有所帮助。