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


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

#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 :: enable_if'中没有名为'type'的类型谁能指出我在这里做错了什么?


1454
2017-11-15 16:19


起源

相关问答: “我的SFINAE发生了什么事”(redux) - HostileFork


答案:


enable_if 因为有效 替换模板参数会导致错误,以及从重载决策集中删除替换,并且编译器仅考虑其他可行的重载。

在您的示例中,实例化成员函数时不会发生替换,因为模板参数 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;
  }
};

11
2017-11-15 16:31



在C ++ 11中,SFINAE规则已被略微修改,因为SFINAE不会在返回类型上触发。总之,这个答案是错误的。 - Nawaz
@Nawaz在gcc4.7.2上运行得很好,因为LWS关闭所以无法发布演示链接。 这里的 一个ideone演示。 - Praetorian
这不是符合C ++ 11标准的代码的证明。 - Nawaz
@Nawaz:C ++ 11标准究竟在哪里说你不能再那样做了?这是一个非常大胆的改变,IMO,我不明白为什么他们应该这样做。此外,如果它是真的,(现在)惯用 template<class T> auto f(T& v) -> decltype(v.foo()); 检查成员的SFINAE构造不起作用。 - Xeo
@Nawaz:这并不意味着返回类型中的替换不再是软错误。其他线程的问题是 内 的 meta<int>,你得到的错误,从来没有发生过 enable_if。 - Xeo


一个简单的解决方案是使用委托给工人 私人的 功能:

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

什么时候 T 是 int, 首先 worker 函数将被调用,因为 static_cast<T*>(0) 原来是类型 int*。在所有其他情况下,将调用worker的模板版本。


2
2017-11-15 16:39



我其实很喜欢这个。我不认为我曾经使用它,但是OP的例子是如此做作,这确实是一个很好的解决方案。 - Kerrek SB
的static_cast <T *>(nullptr) - Janek Olszak


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,或将上述自由函数放入嵌套的成员模板包装器中(这可能是更“正确”的解决方案)。


1
2017-11-15 16:32



我已经看过这个解决方案,但它似乎真的破坏了代码的可读性。 - David Doria
@DavidDoria:原始代码太做作,无法提出更适应的建议。 - Kerrek SB
@DavidDoria如果您的情况是您只使用SFINAE来检查某些类型是否存在 is_same (如果那些不匹配则有默认值)然后模板专用 Point 对于那些固定类型将是你想要的。您将拥有相同的实例,具有更可读的定义。 - HostileFork


根据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();
}

0
2017-11-15 16:44





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

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

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


0
2018-05-18 13:10