【问题标题】:How to define a specialized class method outside of class body in C++?如何在 C++ 中的类主体之外定义一个专门的类方法?
【发布时间】:2021-07-30 10:20:02
【问题描述】:

我有一个模板类A<T> 及其对整数参数的专门化。并且类及其特化都声明了方法foo(),我想在类主体之外定义它:

#include <concepts>

template<class T> 
struct A { static void foo(); };

template<std::integral T>
struct A<T> { static void foo(); };

template<class T>
void A<T>::foo() {}

template<std::integral T>
void A<T>::foo() {}
 
int main() { A<int>::foo(); }

GCC 接受此代码。

Clang 打印错误 https://gcc.godbolt.org/z/hYfYGPfMh

error: type constraint differs in template redeclaration
template<std::integral T>

MSVC 会在两个方法定义上打印错误:

error C3855: 'A<T>': template parameter 'T' is incompatible with the declaration
error C2447: '{': missing function header (old-style formal list?)
error C2065: 'foo': undeclared identifier

请建议如何在类体之外定义方法并使所有编译器满意?

【问题讨论】:

  • 好像是gcc的bug。 see marked answer in other question.
  • 我真诚地希望 gcc 是其中正确的一个,其余的得到修复,否则我看不出应该如何调整用户代码。不过,我不确定章节和诗句。无论如何,把它变成一个language-lawyer-question 并附带一个可行的解决方法?
  • @ГеоргийГуминов 另一个问题是关于 requires 子句,但这里没有
  • @ГеоргийГуминов 可能相关,但明显不同。可能偶然表明 gcc 在这种情况下具有更有用(并且希望是正确的)行为。
  • @ГеоргийГуминов 实际上这里的情况更类似于第二个答案中的“ok”

标签: c++ c++20 c++-concepts partial-specialization


【解决方案1】:

我很确定 MS 和 Clang 编译器在这里都有错误,而 GCC 正在正确编译您的代码。在其他编译器中修复这些错误之前,我建议继续使用概念模式,而不是回到过时的方法。只需使用一个额外的类来解决这个错误:

#include <concepts>
#include <iostream>

// This is a work-around for using concept specialization of
// classes in conjunction with out-of-body definition of members.
// Only helpful for MSVC and Clang. GCC is properly compiling
// out-of-body concept specializations.

template <typename T>
class A
{
    // For MSVC ONLY: the default template seems require being empty
    // for this to work, but do fiddle around with it.
    
    // (Also works with MSVC:)
    A()                         = delete;
    A(const A&)                 = delete;
    A(A&&) noexcept             = delete;
    A& operator =(const A&)     = delete;
    A& operator =(A&&) noexcept = delete;
    ~A()                        = delete;

    // Clang and GCC can have members just fine:
    // static const char* foo();
};

// We use distinct base classes to define our concept specializations of A.
template <std::signed_integral T>
class A_Signed_Integral
{
public:
    static const char* foo();
};
template <std::unsigned_integral T>
class A_Unsigned_Integral
{
public:
    static const char* foo();
};

// And then we wrap them using the actual concept specializations of A,
// making the specializations effectivel the same class as the base class.
template <std::signed_integral T>
class A<T> :
    public A_Signed_Integral<T>
{
public:
    using A_Signed_Integral<T>::A_Signed_Integral;  // grab all ctors
    using A_Signed_Integral<T>::operator =;         // an exceptional case
};
template <std::unsigned_integral T>
class A<T> :
    public A_Unsigned_Integral<T>
{
public:
    using A_Unsigned_Integral<T>::A_Unsigned_Integral;
    using A_Unsigned_Integral<T>::operator =;
};

// Out-of-body definitions can be located to another file
template <std::signed_integral T>
inline const char* A_Signed_Integral<T>::foo()
{
    return "using A<std::signed_integral T> foo";
}

template <std::unsigned_integral T>
inline const char* A_Unsigned_Integral<T>::foo()
{
    return "using A<std::unsigned_integral T> foo";
}

int main()
{
    std::cout << A<signed long>::foo() << std::endl;
    std::cout << A<unsigned long>::foo() << std::endl;

    return 0;
}

(用所有三个编译器测试并且工作正常:see gcc.godbolt.org

在未来,一旦错误被修复,搜索和替换应该会相对容易地删除基类,而只使用 A 的概念特化。

编辑: 更新示例以适用于 MSVC,它似乎还不能使用默认模板。

【讨论】:

  • 谢谢。 GCC 和 Clang 接受您的代码,但 MSVC 打印错误:gcc.godbolt.org/z/6zcsabYa5
  • 啊,感谢您指出@Fedor,您说的很对。与其他相比,MSVC 似乎还有另一个限制。我更新了我验证使用 MS 编译器编译的示例代码。
  • 谢谢,我想你可以简单地声明template &lt;typename T&gt; class A;而不定义它:gcc.godbolt.org/z/Karedb7oz
【解决方案2】:

我不是 C++ 模板专家,我尝试过类似下面的方法

template<class T, bool = std::is_integral_v<T>>
struct A
{};

template<class T>
struct A<T, false>
{ 
   static void foo(); 
};

template<class T>
struct A<T, true> 
{ 
   static void foo(); 
};

template<class T>
void A<T,false>::foo() 
{
  std::cout << "I am working on a non-integral type" << std::endl;
}

template<class T>
void A<T, true>::foo() 
{
  std::cout << "I am working on an integral type" << std::endl;
}

int main()
{
  A<int>::foo();
  A<float> ::foo();

  return 0;
}

代码给了我 MS C++ 编译器的结果

I am working on an integral type
I am working on a non-integral type

【讨论】:

  • 这个众所周知的解决方法的问题在于它改变了主模板,这可能并不总是可以接受的(对 API 的最小更改,对 ABI 的重大更改),甚至是可能的(反向兼容或所有权/控制权)。无论如何,使第二个参数始终为 bool 并制作适当的 SFINAE 条件具有更广泛的适用性。
猜你喜欢
  • 2021-09-26
  • 2012-03-16
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多