如果可能enable_if_t如何将模板限制应用于具有单独实现代码的整个类?

How can you apply enable_if_t template restriction to entire class with separated implementation code, if possible?

提问人:Nick Williams 提问时间:10/1/2023 最后编辑:Nick Williams 更新时间:10/3/2023 访问量:130

问:

注意:我更喜欢始终将我的实现与我的声明分开,即使对于仍必须放在标头中的模板代码也是如此。因此,我倾向于为声明提供一个文件,并在该文件的底部包含一个用于内联和模板实现的文件,然后将我的非内联、非模板实现放在文件中。在这种情况下,可能无法遵循这种偏好,或者我可能做错了什么。.h.hpp.cpp

我有这个初始代码,它编译并运行:

#include <iostream>
#include <memory>
#include <string>

#define OddSource_Export __attribute((visibility("default")))

class IPAddress {};
class IPv4Address : public IPAddress {
public:
    uint8_t version() const {
        return 4;
    }
};
class IPv6Address : public IPAddress {
public:
    uint8_t version() const {
        return 6;
    }
};

template<class TAddress>
class OddSource_Export InterfaceIPAddress
{
public:
    InterfaceIPAddress(TAddress const &, uint16_t);
    uint8_t version() const;
private:
    ::std::unique_ptr<TAddress> const _address;
    uint16_t const _flags;
};

template<class TAddress>
InterfaceIPAddress<TAddress>::
InterfaceIPAddress(TAddress const &address, uint16_t flags)
    : _address(new TAddress(address)),
      _flags(flags)
{}

template<class TAddress>
uint8_t
InterfaceIPAddress<TAddress>::
version() const
{
    return this->_address->version();
}

int main()
{
    IPv4Address addr4;
    InterfaceIPAddress<IPv4Address> address(addr4, 0);
    std::cout << "Version: " << std::to_string(address.version()) << std::endl;
    return 0;
}

但是我想要一个简单的基类限制。因此,我首先定义了这个别名:

template<class TAddress>
using Enable_If_IPAddress = ::std::enable_if_t<::std::is_base_of_v<IPAddress, TAddress>>;

更改我的类声明,如下所示:

template<class TAddress, typename = Enable_If_IPAddress<TAddress>>
class OddSource_Export InterfaceIPAddress

并更改我的构造和方法实现如下:

template<class TAddress, typename>

这是新版本:

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

#define OddSource_Export __attribute((visibility("default")))

class IPAddress {};
class IPv4Address : public IPAddress {
public:
    uint8_t version() const {
        return 4;
    }
};
class IPv6Address : public IPAddress {
public:
    uint8_t version() const {
        return 6;
    }
};

template<class TAddress>
using Enable_If_IPAddress = ::std::enable_if_t<::std::is_base_of_v<IPAddress, TAddress>>;

template<class TAddress, typename = Enable_If_IPAddress<TAddress>>
class OddSource_Export InterfaceIPAddress
{
public:
    InterfaceIPAddress(TAddress const &, uint16_t);
    uint8_t version() const;
private:
    ::std::unique_ptr<TAddress> const _address;
    uint16_t const _flags;
};

template<class TAddress, typename>
InterfaceIPAddress<TAddress>::
InterfaceIPAddress(TAddress const &address, uint16_t flags)
    : _address(new TAddress(address)),
      _flags(flags)
{}

template<class TAddress, typename>
uint8_t
InterfaceIPAddress<TAddress>::
version() const
{
    return this->_address->version();
}

int main()
{
    IPv4Address addr4;
    InterfaceIPAddress<IPv4Address> address(addr4, 0);
    std::cout << "Version: " << std::to_string(address.version()) << std::endl;
    return 0;
}

但是,现在我的构造函数无法编译:

warning: missing 'typename' prior to dependent type name InterfaceIPAddress<TAddress>::InterfaceIPAddress; implicit 'typename' is a C++20 extension [-Wc++20-extensions]
    InterfaceIPAddress<TAddress>::
    ^
    typename 
error: expected ')'
    InterfaceIPAddress(TAddress const &address, uint16_t flags)
                                ^
note: to match this '('
    InterfaceIPAddress(TAddress const &address, uint16_t flags)
                      ^
error: expected ';' at end of declaration
        : _address(new TAddress(address)),
        ^
        ;
error: no template named '_address'; did you mean 'TAddress'?
        : _address(new TAddress(address)),
          ^~~~~~~~
          TAddress

该方法失败并出现类似的错误。这是在 Clang14 上。GCC 也有各种编译器错误,但它们是不同的:version()

error: invalid use of incomplete type ‘class InterfaceIPAddress’
   38 | InterfaceIPAddress(TAddress const &address, uint16_t flags)
      |                                                           ^
main.cpp:26:24: note: declaration of ‘class InterfaceIPAddress’
   26 | class OddSource_Export InterfaceIPAddress
      |                        ^~~~~~~~~~~~~~~~~~

我很确定我定义正确,因为如果我移动我的实现以与声明内联,它会在没有警告或错误的情况下编译。所以这有效:enable_if_t

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

#define OddSource_Export __attribute((visibility("default")))

class IPAddress {};
class IPv4Address : public IPAddress {
public:
    uint8_t version() const {
        return 4;
    }
};
class IPv6Address : public IPAddress {
public:
    uint8_t version() const {
        return 6;
    }
};

template<class TAddress>
using Enable_If_IPAddress = ::std::enable_if_t<::std::is_base_of_v<IPAddress, TAddress>>;

template<class TAddress, typename = Enable_If_IPAddress<TAddress>>
class OddSource_Export InterfaceIPAddress
{
public:
    InterfaceIPAddress(TAddress const & address, uint16_t flags)
        : _address(new TAddress(address)),
          _flags(flags)
    {}
    uint8_t version() const
    {
        return this->_address->version();
    }
private:
    ::std::unique_ptr<TAddress> const _address;
    uint16_t const _flags;
};

int main()
{
    IPv4Address addr4;
    InterfaceIPAddress<IPv4Address> address(addr4, 0);
    std::cout << "Version: " << std::to_string(address.version()) << std::endl;
    return 0;
}

那么,我是否遗漏了一些需要为分离实现执行的额外步骤?还是在使用时无法分离此实现?enable_if_t

此外,我尝试在编译器建议的地方添加一个,但所做的只是满足警告。未解决编译错误。typename

编辑:这应该重新打开

我不明白为什么这个问题被关闭了。它说,“编辑问题以包括所需的行为、特定问题或错误,以及重现问题所需的最短代码。这些编辑已经完成,这个问题不可能比现在更清楚了。另外,它已经有一个完整且公认的答案。关闭这个问题是荒谬的,并且阻碍了其他人找到有用信息的能力。

C++ 模板 C++17 实现 Enable-If

评论

3赞 Alan Birtles 10/2/2023
请展示一个最小的可重复示例。您是否尝试过按照编译器的建议添加一个?typename
0赞 Nick Williams 10/2/2023
按照建议进行了编辑。
0赞 Alan Birtles 10/2/2023
仍然不是一个最小的可重复示例,更像是一个不完整的拼图游戏,我们需要将一些位粘在一起,而其他位缺失
0赞 tbxfreeware 10/2/2023
如果您编辑了帖子以在 MRE 中包含每个翻译单元的完整文件,这将更容易分析。优选地,每个文件都以给出其名称的注释开头,例如,、、、和。这样,我们可以很容易地尝试自己编译您的代码。// main.cpp// A.h// A.hpp// A.cpp
0赞 Nick Williams 10/2/2023
我已经重写了这个问题。

答:

3赞 康桓瑋 10/2/2023 #1

由于您为类添加了额外的模板参数,因此需要将其传递到实现声明中,例如

template<class TAddress, typename Enable>
InterfaceIPAddress<TAddress, Enable>::
InterfaceIPAddress(TAddress const &address, uint16_t flags)
    : _address(new TAddress(address)),
      _flags(flags)
{
}

使用简化的示例进行演示

但是,对于您的示例,仅使用是更合适的选项,它消除了额外的模板并提供更好的诊断:static_assert

template<class TAddress>
class OddSource_Export InterfaceIPAddress
{
  static_assert(std::is_base_of_v<IPAddress, TAddress>, 
    "the template parameter TAddress must derived from IPAddress");
public:
  // ...
};

评论

1赞 康桓瑋 10/2/2023
@NickWilliams是命名未命名的类型名,以便于传入以符合类的定义,该定义接受两个模板参数。, typename Enable>EnableInterfaceIPAddress<TAddress, Enable>::InterfaceIPAddress
1赞 tbxfreeware 10/2/2023
你也改成了吗?InterfaceIPAddress<TAddress>::InterfaceIPAddress<TAddress, Enable>::
1赞 tbxfreeware 10/2/2023
我认为我们看到了函数的模板参数推导和类的 CTAD 之间的区别,但我可能是错的。
1赞 tbxfreeware 10/2/2023
你正在做的事情没有错。有几种流行的使用方式,包括您使用它的方式。SFINAE 最初并不是作为元编程工具而设计的。毫不奇怪,它生成的错误消息不如您可以在 .有关其他一些习语,请参阅 CppReference 中的示例...std::enable_if_tstatic assertenable_if
1赞 tbxfreeware 10/2/2023
但是请注意,其中许多正在被 C++20 中的 requires 子句所取代。它甚至比 更干净,例如: 。static_asserttemplate<class TAddress> requires std::is_base_of_v<IPAddress, TAddress> class InterfaceIPAddress { ... }