【问题标题】:C++ Template Specialization and SubclassingC++ 模板特化和子类化
【发布时间】:2019-05-13 00:31:54
【问题描述】:

我想知道是否可以让模板特化接受一个类及其子类。像这样:

class A {};

class B : public A {};

template <typename T>
void foo(const T& t) {
  printf("T");
}

template <>
void foo(const A& t) {
  printf("A");
}

int main(int argc, char** argv) {
  B b;
  foo(b);

  return 0;
}

目前它输出'T',因为b没有自己的模板特化,所以它默认打印'T'。我想知道B 是否可以使用A 的模板特化,因为BA 的子类。还是那根本不是一回事?

注意:由于一些要求,我不能使用复制/移动。

注意:如果我不需要更改 AB,我也更愿意,但让我们先看看有什么可能。

【问题讨论】:

  • foo(static_cast&lt;A const &amp;&gt;(b));?或者干脆foo&lt;A&gt;(b)
  • 第二个函数不需要是函数模板。请考虑我的回答。
  • @Jackblue 我写了一个答案。如果它解决了问题,请考虑投票/接受它! (stackoverflow.com/help/someone-answers)

标签: c++ templates inheritance


【解决方案1】:

问题是,当T被推导出为B时,主模板是完全匹配的;比专精更适合。

您可以改用模板重载;与SFINAE

template <typename T>
std::enable_if_t<!std::is_base_of_v<A, T>> foo(const T& t) {
  printf("T");
}

template <typename T>
std::enable_if_t<std::is_base_of_v<A, T>> foo(const T& t) {
  printf("A");
}

LIVE

【讨论】:

    【解决方案2】:

    可能有更简洁的方法来做到这一点。但是,如果您将 foo 的实际实现更改为像 std::hash 这样的 SFINAE functionoid object,您可以保持默认重载,而不会因所有潜在的重载条件而污染它。 (信用Arthur O'Dwyer's blog)。

    class A {};
    
    class B : public A {};
    
    template <typename T, typename Enable = void>
    struct FooImpl {
        static void foo(const T& a) {
            printf("B");
        }
    };
    
    template <typename T>
    struct FooImpl<T, std::enable_if_t<std::is_base_of_v<A, T>>> {
    static void foo(const T& a) {
        printf("A");
    }
    };
    
    template <typename T>
    void foo(const T& t) {
        FooImpl<T>::foo(t);
    }
    

    【讨论】:

    • 调用函数并非没有开销。请考虑我的回答,这也是相似的。
    • @AKL 任何优化器都会完全内联所有这些foo 函数,使它们等同于直接调用printf
    • @aschepler ,总的来说你是对的!但是,如果有一个开销较低的解决方案,最好还是实现它!
    • @aschepler ,现在我想我不确定你是否是对的!如果选择关闭优化怎么办? “尽可能以最低的开销编写程序并希望得到最好的优化”不应该是座右铭吗?
    • @AKL 我不这么认为。如果关闭优化会使程序变得太慢,那么这并不奇怪。但即使进行了优化,也并非总是更快的功能更好。真正的瓶颈发生在一小部分代码中,所以你永远不会注意到与其他 98+% 的巧妙技巧的区别,除非旧代码的算法选择很差。但是当一个聪明的技巧使源代码更难理解/修改,或者更容易出现错误时,作为开发人员你会感到痛苦。正如他们所说,“过早的优化是万恶之源”。
    【解决方案3】:

    这个怎么样:

    foo(static_cast<A&>(b));
    

    【讨论】:

      【解决方案4】:

      绝对有可能,不需要
      -复制/移动,
      -改变班级,
      -更改函数体(包括但不限于调用另一个类模板静态函数),
      -改变函数的返回类型,
      - 甚至使用一个常量表达式作为函数的返回类型/s

      解决方案

      只需要将通用函数作为一个函数模板,它采用SFINAE技术以默认模板类型参数的形式用于额外的模板类型参数,以避免特殊情况并具有专业化/s 正常功能/秒:

      template <typename T, typename = std::enable_if_t<!std::is_base_of_v<A, T> > >
      void foo(const T& t) {
          printf("T");
      }
      void foo(const A& t) {
          printf("A");
      }
      
      
      

      说明:

      对于一般情况,默认模板类型参数 std::enable_if_t&lt;!std::is_base_of_v&lt;A, T&gt; &gt; &gt; 可以从第一个模板类型参数T 推导出来。因为存在且定义明确,所以会调用函数模板。

      当使用基于类A 的类型的对象调用函数时,因为未定义std::enable_if_t&lt;!std::is_base_of_v&lt;A, T&gt; &gt; &gt;,默认模板类型参数不存在,因此无法推断模板类型参数。所以编译器会寻找另一个具有相同名称和相似参数类型的函数,所以正常的函数 void foo(const A&amp; t) { printf("A");} 会被调用,不会有歧义。

      使用注意事项

      对于新的专业化,只需向(一个)函数模板添加一个类似的伪类,并为新的专业化编写一个函数(非模板)。

      如果模板默认类型参数看起来很大且令人困惑,可以简单地创建一个策略模板并使用它来代替。喜欢:

      template<typename T, typename P>
      using exclude =  std::enable_if_t<!std::is_base_of_v<P, T> >;
      template <typename T, typename = exclude<T,A> >
      void foo(const T& t) {
          printf("T");
      }
      
      

      另外由于在 C++17 之前没有启用某些功能,对于较低的 C++ 版本,可以编写如下模板:

      template <typename T, typename  = typename std::enable_if<!std::is_base_of<A, T>::value>::type>
      

      另外,如果选择使用@songyuanyao的解决方案,它使用常量表达式作为函数的返回类型,如果函数的返回类型不是void,例如@ 987654332@, 解决方案变成这样:

      template <typename T>
      std::enable_if_t<!std::is_base_of_v<A, T>, return_type> foo(const T& t) {
        printf("T");
        return_type return_value;
        return return_value;
      }
      
      template <typename T>
      std::enable_if_t<std::is_base_of_v<A, T>, return_type> foo(const T& t) {
        printf("A");
        return_type return_value;
        return return_value;
      }
      

      进一步的例子

      最后,为了更好地理解SFINAE,可以考虑一种不需要任何库的不一般正确/不包罗万象的替代解决方案:

      template<bool>
      struct ifnot;
      template<>
      struct ifnot<false> {
          enum {v};
      };
      template<typename T, typename P>
      struct test {
          static T value_of_T();
          static char check(...);
          static int check(P);
          enum {v = sizeof(check(value_of_T())) - 1};
      };
      
      template <typename T, bool = ifnot<test<T, A>::v>::v>
      void foo(const T& t) {
          printf("T");
      }
      void foo(const A& t) {
          printf("A");
      }
      
      

      虽然此解决方案也适用于此特定示例,但请注意此解决方案并不总是正确的。因为它只测试 T 到 A 的转换(即使它本身也不完整且有问题)而不是继承。特别是对于这些应该用类似类型的对象调用的函数,很有可能这些类型中的许多类型可以相互转换!

      我认为继承测试的正确方法包括非相​​互转换测试和确定是否没有类型是void*。综合考虑 使用 std::is_base_of 或 std::is_base_of_v 要好得多。不过struct ifnot 是可以的,甚至可以用std::enable_if 换取它,并适当更改它们的用法。

      祝你好运!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-08
        • 2022-07-05
        • 2012-11-06
        • 1970-01-01
        相关资源
        最近更新 更多