【问题标题】:How to declare specialization template method outside class?如何在类外声明特化模板方法?
【发布时间】:2017-10-02 13:59:01
【问题描述】:

Converter 类旨在将字符串转换为另一种类型。所以如果目标也是一个字符串,那么它应该只返回原始值的副本。

工作代码是这样的:

template<typename C>
class Converter {
public:
    template<typename T>
    static T To(const std::wstring& obj);

    template<>
    static std::wstring To(const std::wstring& obj) {
        return obj;
    }
};

template<typename C>
template<typename T>
T Converter<C>::To(const std::wstring& obj) {
    // Conversion
    return T();
}

(适用于 VC++2015 Update 3)

我尝试将专业化方法移到类声明之外,如下所示:

template<typename C>
template<>
std::wstring Converter<C>::To<std::wstring>(const std::wstring& obj) {
    return obj;
}

还有几个编译错误:

错误 C3212 'Converter::To':a 的显式特化 模板成员必须是显式特化的成员

错误 C2768 'Converter::To':非法使用显式模板 论据

【问题讨论】:

  • 你的“工作代码”doesn't even work.
  • 我认为根据标准,两者实际上都是非法的,但出于某种原因,MSVC 还是允许第一个。两者都不能在 GCC 下工作。看起来如果不专门化外部模板,您就无法明确专门化内部模板。
  • @cdhowie 在 VC++2015 上工作。
  • @cdhowie 它被标记为 [visual-c++]。它适用于 Visual C++。
  • 旁注:您在第一个示例中的第二个声明不是专业化。特化将指定模板的参数:To&lt;std::wstring&gt;。如果可以创建一个没有任何参数的函数模板,我会说它更接近于重载。

标签: c++ templates visual-c++


【解决方案1】:

明确禁止您尝试做的事情:

[temp.expl.spec]/16 在为类模板的成员或出现在命名空间范围内的成员模板的显式特化声明中,成员模板及其一些封闭类模板可以保持非特化,除了如果它的封闭类模板也没有显式特化,则声明不应显式特化类成员模板 [强调我的]... [ 示例: em>

template <class T1> class A {
  template<class T2> class B {
    template<class T3> void mf1(T3);
    void mf2();
  };
};
template <> template <class X>
  class A<int>::B {
    template <class T> void mf1(T);
  };
template <> template <> template<class T>
  void A<int>::B<double>::mf1(T t) { }

template <class Y> template <>
  void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but
                                  // its enclosing class template A is not

结束示例 ]

【讨论】:

    【解决方案2】:

    您可以改用标签调度:

    template <typename> struct Tag{};
    
    template<typename C>
    class Converter {
    
        template<typename T>
        static T To(const std::wstring& obj, Tag<T>);
    
        static std::wstring To(const std::wstring& obj, Tag<std::wstring>);
    
    public:
        template<typename T>
        static T To(const std::wstring& obj) { return To(obj, Tag<T>{}); }
    };
    

    然后

    template<typename C>
    template<typename T>
    T Converter<C>::To(const std::wstring& obj, Tag<T>) {
        // Conversion
        return T();
    }
    
    template<typename C>
    std::wstring Converter<C>::To(const std::wstring& obj, Tag<std::wstring>) {
        return obj;
    }
    

    【讨论】:

      【解决方案3】:

      经验法则:

      更喜欢函数重载而不是专门化!

      在你的情况下使用重载和sfinae:

      #include <type_traits>
      #include <string>
      
      template<typename C>
      class Converter {
      public:
          template<typename T, typename std::enable_if<!std::is_same<T, std::wstring>::value>::type* = nullptr >
          static T To(const std::wstring& obj) {
      
          }
      
          template<typename T, typename std::enable_if<std::is_same<T, std::wstring>::value>::type* = nullptr >
          static std::wstring To(const std::wstring& obj) {
      
          }
      };
      

      [live demo]

      【讨论】:

      • 有没有办法在类声明之外声明这两个方法?
      • @user1633272 here你去
      • @user1633272 你不能在std::is_same 中使用typename Converter&lt;CharType&gt;::StringType 吗?
      • 不,我不能使用它,请参阅here,该代码在 VC++2015 上不起作用。但是我应该能够简化 std::basic_string 的第二个参数。这是code
      • 错误信息是:无法将函数定义与VC2015中的现有声明匹配。
      【解决方案4】:

      c++ 不支持,Igor 的回答是正确的。还是有窍门的。

      但首先 - 为什么真的需要它?

      在我的例子中:假设我只想通过返回类型提供模板参数来“重载”函数,并且我不想在具有大定义的标题中弄得一团糟。

      该案例与作者不同,但问题和解决方案是相同的。

      诀窍是为每个显式模板制作一个没有模板的对应方法:

      // SomeClass.h
      template <typename ClassType>
      class SomeClass
      {
      public:
          template <typename ReturnType> ReturnType Get(const std::string& parameter) const;
      
          template <>
          inline int Get<int>(const std::string& parameter) const { return GetInt(parameter); }
      
          template <>
          inline float Get<float>(const std::string& parameter) const { return GetFloat(parameter); }
      
          template <>
          inline std::string Get<std::string>(const std::string& parameter) const { return GetString(parameter); }
      
      private:
          int GetInt(const std::string& parameter) const;
          float GetFloat(const std::string& parameter) const;
          std::string GetString(const std::string& parameter) const;
      }
      
      
      // SomeClass.inl
      template <typename ClassType>
      int SomeClass<ClassType>::GetInt(const std::string& parameter) const { ... }
      
      template <typename ClassType>
      float SomeClass<ClassType>::GetFloat(const std::string& parameter) const { ... }
      
      template <typename ClassType>
      std::string SomeClass<ClassType>::GetString(const std::string& parameter) const { ... }
      

      由于这些方法是私有的,您可以为它们指定任何您想要的长垃圾名称。

      现在它的用法如下所示:

      SomeClass<WhateverType> a;
      
      int count = a.Get<int>("count");
      float width = a.Get<float>("width");
      std::string name = a.Get<std::string>("name");
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多