基于不同枚举值的类成员重载(或特化)

时间:2016-06-23 01:29:10

标签: c++ templates c++11 sfinae enable-if

是否可以根据给定的枚举值重载或专门化类成员函数?

enum class Type {
  TypeA,
  TypeB,
  TypeC
};

class Foo {

  public:

  template <Type t, typename R = std::enable_if_t<t==Type::TypeA, int>>
  R get() {
    return 1;
  }

  template <Type t, typename R = std::enable_if_t<t==Type::TypeB, double>>
  R get() {
    return 2;
  }

  template <Type t, typename R= std::enable_if_t<t==Type::TypeC, float>>
  R get() {
    return 3;
  }
};

Foo foo;

std::cout << foo.get<Type::TypeA>() << std::endl;
std::cout << foo.get<Type::TypeB>() << std::endl;
std::cout << foo.get<Type::TypeC>() << std::endl;

编译抱怨上面的代码片段重载。

3 个答案:

答案 0 :(得分:5)

修复它的一种非常标准的方法是将std::enable_if子句放在函数的返回类型中,而不是像这样的模板参数中。

这是以c ++ 11标准编译的。

#include <iostream>
#include <type_traits>

enum class Type {
  TypeA,
  TypeB,
  TypeC
};

class Foo {

  public:

  template <Type t>
  typename std::enable_if<t==Type::TypeA, int>::type get() {
    return 1;
  }

  template <Type t>
  typename std::enable_if<t==Type::TypeB, double>::type get() {
    return 2;
  }

  template <Type t>
  typename std::enable_if<t==Type::TypeC, float>::type get() {
    return 3;
  }
};

static_assert(std::is_same<int, decltype( std::declval<Foo>().get<Type::TypeA>())>::value, "");
static_assert(std::is_same<double, decltype( std::declval<Foo>().get<Type::TypeB>())>::value, "");
static_assert(std::is_same<float, decltype( std::declval<Foo>().get<Type::TypeC>())>::value, "");

int main() {

Foo foo;

std::cout << foo.get<Type::TypeA>() << std::endl;
std::cout << foo.get<Type::TypeB>() << std::endl;
std::cout << foo.get<Type::TypeC>() << std::endl;

}

我不确定我是否可以详细解释为什么这种变化会对编译器产生如此大的影响。

但是,请考虑以下事项。对于您的版本,虽然您实际上从未使用两个显式模板参数实例化get,但从技术上讲,这三个成员函数模板可以“碰撞”并生成具有完全相同名称的函数。因为,如果您确实实例化了get<Type::TypeB, int>,那么它将具有相同的返回类型,输入参数和名称get<Type::TypeA>。 C ++不支持函数模板特化,它会使重载决策规则变得非常复杂。因此,具有可能发生碰撞的功能模板可能会使编译器非常沮丧。

当你按照我展示的方式进行时,模板不可能碰撞并生成具有相同名称和签名的函数。

答案 1 :(得分:3)

您可以避免使用std::enable_if映射Type和返回类型专门化一个简单的结构(Bar,在以下示例中)

#include <iostream>

enum class Type {
   TypeA,
   TypeB,
   TypeC
};

template <Type t>
struct Bar;

template <>
struct Bar<Type::TypeA>
 { using  type = int; };

template <>
struct Bar<Type::TypeB>
 { using  type = double; };

template <>
struct Bar<Type::TypeC>
 { using  type = float; };


class Foo {

   public:

      template <Type t>
         typename Bar<t>::type get();
};

template <>
Bar<Type::TypeA>::type Foo::get<Type::TypeA> ()
 { return 1; }

template <>
Bar<Type::TypeB>::type Foo::get<Type::TypeB> ()
 { return 2.2; }

template <>
Bar<Type::TypeC>::type Foo::get<Type::TypeC> ()
 { return 3.5f; }


int main ()
 {
   Foo foo;

   std::cout << foo.get<Type::TypeA>() << std::endl;
   std::cout << foo.get<Type::TypeB>() << std::endl;
   std::cout << foo.get<Type::TypeC>() << std::endl;

   return 0;
 }

答案 2 :(得分:0)

您的示例无法编译,因为默认类型模板化参数不是函数签名的一部分。因此就编译器而言,您要多次定义相同的函数,这是非法的。相反,您需要使用默认的非类型模板参数。

class Foo {

  public:

  template <Type t, std::enable_if_t<t==Type::TypeA, int> = 0>
  int get() {
    return 1;
  }

  template <Type t, std::enable_if_t<t==Type::TypeB, int> = 0>
  double get() {
    return 2;
  }

  template <Type t, std::enable_if_t<t==Type::TypeC, int> = 0>
  float get() {
    return 3;
  }
};

已经发布了两个工作解决方案;那我为什么要发这个呢?好吧,我更喜欢它。具体来说:

  • 它保留了函数实际所需的内容,并在启用时将其返回。这使得该功能更易于阅读和理解。您可以将第二个参数的代码段拖到一个名为REQUIRE或类似的微小宏中;这使得非常清楚发生了什么。发布的其他两个答案都没有这个属性。
  • 这种技术更具普遍性;它可以在略有不同的情况下用于构造函数,它不会返回任何内容,也不能与其他两个解决方案一起使用

max66将枚举映射到类型的方法很不错,这是你应该注意的事情。虽然你可以一般地写出身体,但它更合适;如果你不得不分别写出每个实现主体,它只添加样板(恕我直言)。您还应该意识到基于专业化的解决方案是脆弱的;如果有第二个模板参数,或者类Foo是类模板,则它不起作用,因为对特化函数模板的限制。

使用上面提到的宏,你可以这样写:

  template <Type t, REQUIRE(t==Type::TypeA)>
  int get() {
    return 1;
  }
  // ...

我认为这是最好的。