您如何在模板 class 中专门化成员函数?

How do you specialize a member function inside a template class?

假设我有以下 class:

template <typename T>
class SomeClass : Parent<T>
{
public:
    // I have a function such as this one:
    T DoSomething(const T &t)
    {
        return t.DoSomething(some_data);
    }

    // But `T` might be a pointer, so sometimes I will need something like the following
    // instead (which obviously doesn't work as given):
    T DoSomething(const T &t)
    {
        return new T(t->DoSomething(some_data));
    }

private:
    XYZ some_data;
};

我试图使用模板专业化以任何可能的半好的方式实现它,但陷入了一大堆模板错误中。

最后我想到了这个非常丑陋的解决方案:

template <typename T>
class SomeClass : Parent<T>
{
public:
    T DoSomething(const T &x)
    {
        return Specializer<T>::Do(this, x);
    }

private:
    template <typename V>
    struct Specializer {
        static V Do(SomeClass *me, const V &x)
        {
            return x.DoSomething(me->some_data);
        }
    };

    template <typename V>
    struct Specializer<V*> {
        static V* Do(SomeClass *me, const V *&x)
        {
            return new V(x->DoSomething(me->some_data));
        }
    };

    XYZ some_data;
};

有没有更好的方法来做到这一点,而不涉及将此函数填充到虚拟 class/struct 并绕过我的 this 指针?

PS:实际上,这与指针无关,而是与不同类型的容器有关。指针只是此处使用的一个简单示例。

使用 SFINAE 怎么样?

例如

#include <utility>
#include <iostream>

template <typename>
struct Parent
 { };

using XYZ = int;

template <typename T>
class SomeClass : Parent<T>
 {
   public:
      template <typename U = T>
      auto DoSomething (T const & t)
       -> decltype( std::declval<U>().DoSomething(std::declval<XYZ>()) )
        { std::cout << "ref\n"; return t.DoSomething(some_data); }

      template <typename U = T>
      auto DoSomething (T const & t)
       -> std::remove_reference_t<
             decltype( std::declval<U>()->DoSomething(std::declval<XYZ>()),
                       std::declval<T>() )>
        {
          using V = std::remove_reference_t<decltype(*t)>;

          std::cout << "pnt\n"; return new V(t->DoSomething(some_data));
        }

   private:
      XYZ some_data;
 };

struct foo
 {
   foo (foo*) {}
   foo () {}

   foo DoSomething (int) const { return {}; }
 } ;

int main()
 {
   SomeClass<foo>   sc1;
   SomeClass<foo*>  sc2;

   foo f;

   sc1.DoSomething(f);
   sc2.DoSomething(&f);
 }

我的意思是:当且仅当 T 是一种支持 DoSomething(XYZ) 方法的类型时启用第一个版本,当且仅当 T 是支持 DoSomething(XYZ) 方法的类型的 指针 ?

您可以避免编写任何特化,并使用像 std::is_pointer 这样的类型特征和 if constexpr 来根据类型是否为指针类型来决定要执行的代码:

auto DoSomething(const T &t)
{
  if constexpr (std::is_pointer_v<T>)
      return new T(t->DoSomething(some_data));
  else    
      return t.DoSomething(some_data);
}

如果您不想检查 T 是否是一个指针,而是想检查其他东西,您仍然可以通过放入 is_pointer 的合适替代品来使用此模式。

如果您有权访问 , you can clean up the need for any SFINAE, specializations, or if constexpr by using concepts and constraints。这只允许您使用不同的实例化标准定义相同的函数 N 次,这在 IMO 中更具可读性。

这与 SFINAE 方法几乎相同,但不需要糟糕的语法(没有 std::declvaldecltype 等)。它也不要求所有实现都存在于一个函数定义中,例如 if constexpr 方法;您所需要的只是具有不同 requires 子句的单独函数定义:

#include <concepts>

...

template <typename T>
class SomeClass : Parent<T>
{
public:
    // Work for everything that's not specialized
    void DoSomething(const T &t) 
    { 
        std::cout << "Basic overload" << std::endl; 
    }

    // Only work for pointers
    void DoSomething(const T& t) requires(std::is_pointer_v<T>) 
    { 
        std::cout << "Pointer overload" << std::endl; 
    }

    // Only work if T is convertible to SomeType
    void DoSomething(const T& t) requires(std::convertible_to<T, SomeType>) 
    { 
        std::cout << "Convertible to SomeType overload" << std::endl; 
    }

private:
    XYZ some_data;
};

Live Example

在这种方法中有 3 个不同的条目:

  • 所有模板的基本回退
  • 适用于任何指针类型的实现,并且
  • 适用于可转换为 SomeType
  • 的任何 T 类型的实现