Search code examples
c++templatesc++17implementationenable-if

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


Note: I prefer to always keep my implementations separate from my declarations, even for template code that must still go in the header. So I tend to have a .h file for the declarations, and at the bottom of that file include a .hpp file for inline and template implementations, then put my non-inline, non-template implementations in .cpp files. It may not be possible to follow that preference in this case, or I may be doing something wrong.

I have this initial code, which compiles and runs:

#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;
}

But I want a simple base class restriction. So I started by defining this alias:

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

Changing my class declaration as follows:

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

And changing my construction and method implementation as follows:

template<class TAddress, typename>

Here's the new version:

#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;
}

However, now my constructor fails to compile:

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

And the version() method fails with similar errors. This is on Clang14. GCC also has various compiler errors, but they are different:

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
      |                        ^~~~~~~~~~~~~~~~~~

I'm pretty sure that I'm defining the enable_if_t correctly, because if I move my implementation to be inline with the declaration, it compiles without warning or error. So this works:

#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;
}

So am I missing some extra step I need to go through for the separated implementation? Or is it not possible to separate this implementation when using enable_if_t?

Also, I tried adding a typename where the compiler suggested, but all that did was satisfy the warning. No compile errors were solved.

EDIT: This should be re-opened

I do not understand why this question was closed. It says, "Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem." Those edits were already performed, and the question could not possibly be any clearer than it now is. Plus, it has a complete and accepted answer already. Closing this question is nonsensical and hinders others' ability to find helpful information.


Solution

  • Since you added an extra template parameter for the class, you need to pass it into the implementation declaration, for example

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

    Demo with your reduced example.

    However, for your example, simply using static_assert is a more appropriate option, which eliminates the extra template and provides better diagnostics:

    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:
      // ...
    };