模板专业化子类

时间:2017-09-20 10:58:07

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

我有一个问题可以最小化到以下示例

#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 << to_str(a) << std::endl;时,代码会运行并打印&#39; aaa&#39;,但是像这样,它会在编译和输出时停止

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)?

感谢。

4 个答案:

答案 0 :(得分:5)

  

我可以'告诉'编译器他们都可以转到同一个函数(一个人接受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
 }

答案 1 :(得分:1)

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

  1. BA私下继承。在这种情况下,我们无法真正将B视为A
  2. 如果您将继承更改为public,那么我们可以尝试强制执行B

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

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

    1. to_str的{​​{1}}专业化接受。这很重要,因为即使我们想强制A进入,我们最终切割对象,这很重要,因为

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

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

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

        std::string
        1. 我们在A中编写了一个class A{ public: std::string chr; A():chr{"aaa"}{} }; class B : public A{ public: B(){chr = "bbb";} }; 方法,派生类可以覆盖它。
        2. 示例:

          virtual const char* get_char()

          请注意,此时我们仍然强制每个A成为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; ,这会引导我进入下一点

          1. B将始终完全匹配您未明确专注的每种类型,在最坏的情况下是首选,并在最佳情况下引起歧义。

            如果你对这个功能没有任何控制权,那么我们就会被我们所拥有的东西所困扰。但是,如果我们,那么我们可以使用A来接受我们需要的任何内容,以接受从template <class T> std::string to_str(T) = delete;派生的任何内容。

          2. 通过这种方式,我们可以保留您的私人继承权,同时保留您重新声明的type_traits成员,同时禁用A其他所有不要求我们{{1}我们的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; } 函数专用于{{ 1}}(作为重载)打印该成员。

            编辑:我忘记了最后一个音符。问题#6:A中没有protected std::string chr个成员。因此,您永远无法to_string指向任何派生类的A&指针。

答案 2 :(得分:1)

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

你想要的是重载分辨率,可能还有标签调度。

首先完全删除它:

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(或A的{​​{1}}子对象。

B

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

的不必要副本
A

答案 3 :(得分:-3)

  

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

一切都应该有效