使用不同的enable_if条件选择成员函数

时间:2012-11-15 16:19:11

标签: c++ templates c++11 sfinae specialization

我正在尝试根据类模板参数确定调用哪个版本的成员函数。我试过这个:

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  void MyFunction(typename std::enable_if<std::is_same<T, int>::value, T >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  void MyFunction(typename std::enable_if<!std::is_same<T, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}

我认为是“如果T是int,则使用第一个MyFunction,如果T不是int,则使用第二个MyFunction,但是我得到编译器错误”错误:'struct std ::中没有名为'type'的类型enable_if'“。有谁能指出我在这里做错了什么?

6 个答案:

答案 0 :(得分:12)

enable_if的作用是因为substitution of a template argument resulted in an error,所以替换从重载决策集中删除,编译器只考虑其他可行的重载。

在您的示例中,实例化成员函数时不会发生替换,因为当时已知模板参数T。实现您正在尝试的最简单方法是创建一个默认为T的虚拟模板参数,并使用它来执行SFINAE。

template<typename T>
struct Point
{
  template<typename U = T>
  typename std::enable_if<std::is_same<U, int>::value>::type
    MyFunction()
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U = T>
  typename std::enable_if<std::is_same<U, float>::value>::type
    MyFunction()
  {
    std::cout << "T is not int." << std::endl;
  }
};

修改

正如HostileFork在评论中提到的那样,原始示例使用户可以明确指定成员函数的模板参数并获得不正确的结果。以下内容应防止成员函数的显式特化编译。

template<typename T>
struct Point
{
  template<typename... Dummy, typename U = T>
  typename std::enable_if<std::is_same<U, int>::value>::type
    MyFunction()
  {
    static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
    std::cout << "T is int." << std::endl;
  }

  template<typename... Dummy, typename U = T>
  typename std::enable_if<std::is_same<U, float>::value>::type
    MyFunction()
  {
    static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
    std::cout << "T is not int." << std::endl;
  }
};

答案 1 :(得分:2)

一个简单的解决方案是将委托用于worker 私有函数:

template<typename T>
struct Point
{

  void MyFunction()
  {
     worker(static_cast<T*>(0)); //pass null argument of type T*
  }

private:

  void worker(int*)
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U>
  void worker(U*)
  {
    std::cout << "T is not int." << std::endl;
  }
};

Tint时,将调用第一个worker函数,因为static_cast<T*>(0)的类型为int*。在所有其他情况下,将调用worker的模板版本。

答案 2 :(得分:1)

enable_if仅适用于推导的函数模板参数或专用的类模板参数。你正在做什么不起作用,因为很明显有一个固定的T = int,第二个声明是错误的。

这是如何做到的:

template <typename T>
void MyFreeFunction(Point<T> const & p,
                    typename std::enable_if<std::is_same<T, int>::value>::type * = NULL)
{
    std::cout << "T is int" << std::endl;
}

// etc.

int main()
{
    Point<int> ip;
    MyFreeFunction(ip);
}

另一种方法是为各种类型Point专门化T,或者将上述自由函数放入嵌套的成员模板包装器中(这可能是更“正确”的解决方案)。

答案 3 :(得分:1)

根据Praetorian的建议(但不改变函数的返回类型),这似乎有效:

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  template<typename U = T>
  void MyFunction(typename std::enable_if<std::is_same<U, int>::value, U >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  template<typename U = T>
  void MyFunction(typename std::enable_if<!std::is_same<U, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}

答案 4 :(得分:0)

以下模板可以仅使用int或float作为模板参数T进行实例化。

回答这个问题:这里根据method()调用的模板参数完全调用worker(),但你仍然可以控制类型。

    template<typename T>
    struct Point
    {
        static_assert (
              std::is_same<T, int>()  ||
              std::is_same<T, float>()
            );

        template<typename U>
        void method(U x_, U y_)
        {
            if constexpr (std::is_same<T, U>()) {
                worker(x_, y_);
                return;
            }
            // else 
            worker(
                static_cast<T>(x_),
                static_cast<T>(y_)
            );
            return ;
        }


    private:

        mutable T x{}, y{};

        void worker(T x_, T y_)
        {
            // nothing but T x, T y
        }

    };

上面的worker()当然会工作,即使它被声明为静态。出于某些正当理由。对上述内容的其他扩展很少(也很简单),但让我们坚持回答。

答案 5 :(得分:0)

我认为这遵循@Praetorian的解决方案,但我发现它更容易:

template<typename T>
struct Point
{
    template<typename U = T>
    std::enable_if_t<std::is_same<U, T>::value && std::is_same<T, int>::value>
    MyFunction()
    {
        std::cout << "T is int." << std::endl;
    }

    template<typename U = T>
    std::enable_if_t<std::is_same<U, T>::value && std::is_same<T, float>::value>
    MyFunction()
    {
        std::cout << "T is not int." << std::endl;
    }
};