3

I have a template base class with a template parameter of type bool. The constructor parameter list of this base class depends on whether the template parameter is true or false. I want to derive from this class another template class with a template parameter that is any type. I need this derived class to call the correct constructor of that Base class depending on traits of that type.

The example below is not all-encompassing. Integral or not, the base class template bool could be for any type trait. Also, the type passed to the template parameter of the derived class could be any type.

I've tried several different methods for trying to syntax this thing out, but the closest I've gotten is with the code below. However it errors due to the fact that the derived class needs to be fully specialized.

#include <type_traits>
using namespace std;

template<bool IsIntegral> struct Base 
{
    template<bool IsI = IsIntegral,
        typename I = typename enable_if<IsI>::type>
    Base(int param) {}

    template<bool IsI = IsIntegral,
        typename I = typename enable_if<!IsI>::type>
    Base() {}
};

template<class T> class CL :
    Base<is_integral<T>::value>
{
public:
template<bool U = is_integral<T>::value> CL();

};

template<>
template<class T>
CL<T>::CL<true>() : // error: ISO C++ forbids declaration of ‘CL’ with no type [-fpermissive]
                    // CL<T>::CL<true>() :
                    //                 ^
                    // error: non-type partial specialization ‘CL<true>’ is not allowed
                    // error: no declaration matches ‘int CL<T>::CL()’
                    // CL<T>::CL<true>() :
                    // ^~~~~
    Base(4) { }

template<>
template<class T>
CL<T>::CL<false>()  // error: ISO C++ forbids declaration of ‘CL’ with no type [-fpermissive]
                    // CL<T>::CL<true>() :
                    //                 ^
                    // error: non-type partial specialization ‘CL<true>’ is not allowed
                    // error: no declaration matches ‘int CL<T>::CL()’
                    // CL<T>::CL<true>() :
                    // ^~~~~
    { }

int main () {
    // Base<true> a; // won't compile as expected
    Base<true> a(4);
    // Base<false> b(4); // won't compile as expected
    Base<false> b;
    
    CL<int> c; // integral
    CL<int*> d; // non-integral
    // should work for any types other than int and int*

    return 0;
}

I need to keep the type T generic and can't fully specialize the class CL. What is the correct syntax for this? Is there a workaround?

I'm using gcc-g++ version 8.3.0-6.

Thanks in advance!

lexical
  • 76
  • 1
  • 6

3 Answers3

1

After playing around with this, a little bit, I came up with this, which I guess falls under the category of "workaround", which you are willing to consider:

template<class T> class CL :
    Base<is_integral<T>::value>
{
public:
    CL() : CL{ is_integral<T>{} } {}

    template<typename U=T>
    CL(std::true_type) : Base<true>{4} {}

    template<typename U=T>
    CL(std::false_type)
    {
    }
};
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 1
    This is a nice approach (not too much of a "workaround" imho), but you typically want to make the delegating constructors private, and there is moreover no need to make them templates unless you plan to apply SFINAE for the dummy template parameter `U`. Finally, to assure we actually call the base constructor specialization we inherited from, we may use an injected class name instead of explicitly supplying the template argument list (`CL::Base{4}` instead of `Base{4}`). For other's reference, this technique is typically referred to as _tag dispatch_. – dfrib Jan 18 '21 at 17:47
  • This works great! Thanks for the rapid response. I even tried compiling it on different C++ standards (11, 14 and 17) with no warnings. Awesome! – lexical Jan 18 '21 at 17:49
1

Your derived class should be:

template<class T> class CL :
Base<is_integral<T>::value>{
  public:
     using base_t = Base<is_integral<T>::value>;
     using base_t::base_t;
};

The using base_t::base_t; makes you inherit the constructors from your base class (available since c++11).

In your example you should also change:

CL<int> c; // integral

into, e.g.:

CL<int> c(10); // integral
Mario Demontis
  • 469
  • 3
  • 11
  • 1
    Note that OP wants default constructors in both cases, but where the one where the integral trait holds, want to forward to the base constructor using a constant (or, say, trait-based) value. – dfrib Jan 18 '21 at 17:38
1

C++20 and requires clauses

Prior to C++20, I would recommend tag dispatch to private delegating constructors as shown in @SamVarshavchik's answer.

Once you may use C++20, you can easily construct these kind of relations using a requires clause:

#include <type_traits>

template <bool IsIntegral>
struct Base  {
    Base(int param) requires IsIntegral {}
    Base() requires (!IsIntegral) {}
};

template<typename T> 
class CL : Base<std::is_integral<T>::value> {
    static constexpr bool kIsIntegral = std::is_integral_v<T>;
 public:
    CL() requires kIsIntegral : CL::Base(4) {}
    CL() requires (!kIsIntegral) : CL::Base() {}    
};
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 1
    Awesome! I compiled this with flags -std=c++2a and -fconcepts and it works great! Unfortunately, I do need to support older standards, but this is a great solution for C++20! – lexical Jan 18 '21 at 17:56