Bilentor
Bilentor

Reputation: 496

Conditional class template constructor

I am trying to make a class that has conditional members as below (sample code to illustrate problem):

template<bool b>
struct conditional_members {};

template<>
struct conditional_members<true> 
{ int m; };

template<typename T, bool b>
struct my_class : public conditional_members<b>
{
    T n;

    // constructor for case when b is false
    my_class(T n) : n(n) {};

    // constructor for case when b is true
    my_class(T n, int m) : n(n), m(m) {};
};

I need to have two conditional constructors depending on the bool b but this does not compile. I tried specializing the constructors with bool value:

template<typename T>
my_class<T, true>::my_class(T n, int m) : n(n), m(m) {};

template<typename T>
my_class<T, false>::my_class(T n) : n(n) {};

but that doesn't compile either because partial function template specializations are not allowed.

Is there a way to achieve this?

Upvotes: 0

Views: 845

Answers (1)

aschepler
aschepler

Reputation: 72291

The problem with

// constructor for case when b is true
my_class(T n, int m) : n(n), m(m) {};

is that a constructor's mem-initializer-list can name only virtual base classes, direct base classes, and direct non-static data members, but never an inherited member like m. This is because the member of a base class is initialized by the base class subobject constructor, so it can't be initialized again (though it could be assigned).

You can instead specify the base class initializer. With this example, conditional_members is an aggregate, so aggregate initialization will work:

// constructor for case when b is true
my_class(T n, int m) : n(n), conditional_members<b>{m} {};

Though with just that, you might get some strange side effects from the fact that my_class specializations always have the two constructors declared, even if it might be invalid to actually instantiate one constructor or the other.

Here's an SFINAE trick to make the constructors conditionally effectively invisible, depending on b:

#include <type_traits>

// Define conditional_members as before.

template<typename T, bool b>
class my_class : public conditional_members<b>
{
    T n;

public:
    // constructor for case when b is false
    template <typename = std::enable_if_t<!b>>
    my_class(T n) : n(n) {}

    // constructor for case when b is true
    template <typename = std::enable_if_t<b>>
    my_class(T n, int m) : conditional_members<b>{m}, n(n) {}
};

As a preview, with C++20 constraints, you'll be able to write it this nice, simpler way instead:

template<typename T, bool b>
class my_class : public conditional_members<b>
{
    T n;

public:
    // constructor for case when b is false
    my_class(T n) requires(!b) : n(n) {}

    // constructor for case when b is true
    my_class(T n, int m) requires(b) : conditional_members<b>{m}, n(n) {}
};

Upvotes: 4

Related Questions