【问题标题】:template specialization sub class模板特化子类
【发布时间】:2017-09-20 10:58:07
【问题描述】:

我有一个问题可以最小化到下面的例子

#include <iostream>
#include <string>


class A{
  public:
    const char* chr;
    A(){chr = "aaa";}
};

class B : A{
  public:
    const char* chr;
    B(){chr = "bbb";}
};

template <class T>
std::string to_str(T) = delete;

template<>
inline std::string
to_str<A>(A object) {
    std::string str;
    return str.assign((object.chr));
}

int main() {
  A a;
  B b;
  std::cout << to_str(b) << std::endl;
}

将其更改为std::cout &lt;&lt; to_str(a) &lt;&lt; std::endl; 时,代码运行并打印'aaa',但像这样,它在编译时停止并输出

main.cpp: In function 'int main()':
main.cpp:30:24: error: use of deleted function 'std::__cxx11::string to_str(T) [with T = B; std::__cxx11::string = std::__cxx11::basic_string<char>]'
   std::cout << to_str(b) << std::endl;
                        ^
main.cpp:18:13: note: declared here
 std::string to_str(T) = delete;
             ^~~~~~

exit status 1

现在假设我有很多继承 A 的类,我可以“告诉”编译器它们都可以转到同一个函数(接受 A)吗?

谢谢。

【问题讨论】:

  • Java 没有有模板。试图在泛型和模板之间找到一些语法之外的相似之处会造成混淆。
  • 您真的需要模板吗?如果没有,那么您只需要 1 个通过 (const) 引用获取 A 的常规函数​​...
  • @DsCpp:在课堂上是否必须成为const char*
  • @DsCpp:您是否可以控制为to_str 编写的专业化?
  • B 是否必须有一个隐藏A 中相同类型成员的成员?您想在to_string 中引用哪个?

标签: c++ c++11 templates inheritance


【解决方案1】:

我可以“告诉”编译器他们都可以使用同一个函数(接受 A)吗?

是的,使用 SFINAE 和 std::is_base_of

template <typename T>
typename std::enable_if<std::is_base_of<A, T>::value, std::string>::type
      to_str (T const & t)
 { return t.chr; }

以下是一个完整的工作示例

#include <type_traits>
#include <iostream>
#include <string>

struct A     { char const * chr; A() : chr{"aaa"} {} };
struct B : A { char const * chr; B() : chr{"bbb"} {} };
struct C     { char const * chr; C() : chr{"ccc"} {} };    

template <typename T>
typename std::enable_if<std::is_base_of<A, T>::value, std::string>::type
      to_str (T const & t)
 { return t.chr; }

int main()
 {
   A a;
   B b;
   C c;

   std::cout << to_str(a) << std::endl;    // print aaa
   std::cout << to_str(b) << std::endl;    // print bbb
   // std::cout << to_str(c) << std::endl; // compilation error
 }

【讨论】:

    【解决方案2】:

    我怀疑这是否真的是您问题的最小化示例。我认为发生的情况是一些重要的细节在翻译中丢失了,因为你在这里展示给我们的代码有很多问题。

    1. B 私下继承自 A。在这种情况下,我们无法真正将B 视为A

    如果您将继承更改为public,那么我们可以尝试像这样强制B

    class B : public A{/*...*/};
    // ...
    std::cout << to_str(*static_cast<A*>(&b)) << std::endl;
    

    但输出将保持“aaa”,这将引导我进入下一个点

    1. 您的 to_str 专业化为 A 接受 。这很重要,因为即使我们想强制输入 B,我们最终也会切片对象,这很重要,因为

    2. B 重新定义了const char* chr 有效地隐藏 A::chr,并且由于我们已经切片,因此无法恢复Bchr

    我们可以开始修复问题,但首先通过 reference 接受A(或const 引用)来修复切片,并且总是 em> 更喜欢函数的重载而不是模板特化:

    std::string to_str(A& object) {/*...*/}
    
    1. 下一个问题是无法直接从A 的实例中恢复Bchr。我们可以在这里采取两种方式之一。
      1. A 中使用std::string 成员并且不要在任何派生类中重新声明它,然后派生类可以在初始化时设置它。

    例子:

    class A{
      public:
        std::string chr;
        A():chr{"aaa"}{}
    };
    
    class B : public A{
      public:
        B(){chr = "bbb";}
    };  
    
    1. 我们在A 中编写了一个virtual const char* get_char() 方法,派生类可以覆盖该方法。

    例子:

    class A{
      public:
        const char* chr;
        A(){chr = "aaa";}
        virtual const char* get_chr() const{return chr;}
    };
    
    class B : public A{
      public:
        const char* chr;
        B(){chr = "bbb";}
        const char* get_chr() const override {return chr;}
    };
    
    template <class T>
    std::string to_str(T) = delete;
    
    std::string to_str(A& object) {
        std::string str;
        return str.assign((object.get_chr()));
    // ...
    std::cout << to_str(*static_cast<A*>(&b)) << std::endl;
    

    请注意,此时我们仍将每个 B 强制为 A,这将引导我进入下一点

    1. template &lt;class T&gt; std::string to_str(T) = delete; 将始终与您未明确专门针对的每种类型完全匹配,在最坏的情况下是首选,在最好的情况下会导致歧义。

      如果您对此功能没有任何控制权,那么我们将无法使用现有的功能。但是,如果我们这样做,那么我们可以使用type_traits 来接受从A 派生的任何内容。

    通过这种方式,我们可以保留您的私有继承,并保留您重新声明的 chr 成员,同时禁用 to_str 的其他所有内容并且不需要我们 static_cast 我们的 b .

    例子:

    #include <type_traits>
    
    class A{
      public:
        const char* chr;
        A(){chr = "aaa";}
    };
    
    class B : A{
      public:
        const char* chr;
        B(){chr = "bbb";}
    };
    
    template<class T, class = std::enable_if_t<std::is_base_of<A, T>::value, int>>
    inline std::string to_str(T& object) {
        std::string str;
        return str.assign((object.chr));
    }
    
    int main() {
      A a;
      B b;
      std::cout << to_str(b) << std::endl;
    }
    

    总的来说,我认为最好的方法是给A 一个protected std::string chr,每个派生类在初始化时设置,然后让你的to_string 函数专门用于A&amp;(作为 overload) 打印该成员。


    编辑:我忘记了最后一个注释。问题 #6:A 中没有 virtual 成员。因此,您将永远无法将dynamic_cast 指向A 的指针指向任何派生类。

    【讨论】:

      【解决方案3】:

      模板函数专业化不能这样工作。没有重载决议;它只允许用您专门的模板参数替换具有特定模板参数的特定函数体。它很少有用。

      你想要的是重载解析,可能带有标签调度。

      首先将其完全删除:

      template <class T>
      std::string to_str(T) = delete;
      

      接下来,写一个重载:

      inline std::string to_str(A object) {
        std::string str;
        return str.assign((object.chr));
      }
      

      然后完成。重载解析将B 分派给A 重载。

      您的下一个问题是切片。 B两个 成员,名为 chrA::chrB::chr。没有好的理由。此外,您还不必要地复制 A(或 BA 子对象)。

      inline std::string to_str(A const& object) {
        std::string str;
        return str.assign((object.chr));
      }
      

      这避免了A 的不必要副本。

      class A{
        public:
          const char* chr;
          A(){chr = "aaa";}
      };
      
      class B : public A{ // public, not private
        public:
          // const char* chr; // no need for this
          B(){chr = "bbb";}
      };
      

      【讨论】:

      • 我有deja vu的感觉:)
      • 好吧,还有切片需要担心。这很重要,因为 B 重新声明了 chr
      【解决方案4】:

      根据 14.7.3 [temp.expl.spec] 第 1 段,只有未删除的函数模板可以显式特化

      C++ Standard Core Language Defect Reports and Accepted Issues, Revision 97

      所以,如果你改变了

      template <class T>
      std::string to_str(T) = delete;
      

      例如,

      template <class T>
      std::string to_str(T) { return ""; }
      

      一切正常

      【讨论】:

      • 您引用的缺陷报告(也被 in 投票)建议允许专业化。 And the wording in the standard now reflects that.
      • 这既错又错。 DR permits 专门删除了功能模板,而您的更改无法解决 OP 的问题。如果没有用,该链接很有趣;其余答案为负值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多