How can I conditionally define the default-constructor?
Asked Answered
U

8

12

I was thinking of a class like:

template < typename ...Whatever >
class MyClass
{
public:
    static constexpr bool has_default_ctr = Something;

    // I want this only if "has_default_ctr" is "true".
    MyClass();

    //...
};

I don't think I can use a constructor template and std::enable_if for this (because there's no arguments). Am I wrong? If not, is there some other way to do this?

Update answered 25/4, 2012 at 4:54 Comment(3)
Just a quick thought - did you try enable_if and a constructor with an argument with a default value?Coccyx
Could you elaborate why you want to do this?Unification
Ok, I honestly thought this was not possible, I stand corrected and deleted my answer. Isn't this a huge anti pattern though?Undergraduate
S
17

C++11 allows to (reliably) use enable_if-style SFINAE in template arguments:

template<
    // This is needed to make the condition dependent
    bool B = has_default_ctr
    , typename std::enable_if<B, int>::type = 0
>
MyClass();

// When outside of class scope:
// have to repeat the condition for out-of-line definition
template<bool B, typename std::enable_if<B, int>::type = 0>
MyClass::MyClass()
/* define here */

In C++03 you could have used a unary constructor with a defaulted parameter -- the default parameter means that the constructor still counts as a default constructor.

Singularity answered 25/4, 2012 at 5:16 Comment(6)
My god man this trick is so useful to me. I was aware of std::enable_if but never though of using it that wayUnprintable
I think this results in a compile-time error. Unless I am missing something, doesn't typename std::enable_if<B, int>::type = 0 resolve to void = 0 when B = true? class C = typename std::enable_if<B, int>::type would work.Outrage
@LucDanton Ah sorry, I completely missed the int argument that you added to enable_if.Outrage
This doesn't work in standards compliant compilers because SFINAE does not apply in this context, since the condition isn't dependent on the template arguments of the default constructor. If you use this trick, you'll get a compiler error any time you try to instantiate MyClass when has_default_ctr is false, regardless of whether the MyClass default constructor is instantiated.Sixth
@Sixth The condition is dependent and everything works as expectedSingularity
Sorry, I think you are right, and there is a bug in my c++ compiler (Apple LLVM version 7.0.2). I'll log it with them. I cannot remove my downvote at this point unless you edit the answer :'(. FWIW, here's the case that's failing for me: ideone.com/p4p6c4Sixth
K
4

Since the solution with a default parameter is mentioned in the comments, but I needed quite some time to figure out how to do it (made my Enabler class private, which does not work), here the suggested solution, in case anybody is looking for it:

class MyClass {
public:
   // if constructor is supposed to be public,
   // this helper class must be public as well!
   struct Enabler {}; 

   template <class U = Enabler>
   MyClass (std::enable_if_t<has_default_ctr, U> = Enabler {})
   {
      // whatever
   }
};
Keon answered 1/8, 2015 at 10:8 Comment(0)
S
1

To get different definitions for a class depending on some condition, put the dependency calculation in a template argument.

// primary template, no default constructor unless Something is true
template< typename T, bool has_default_ctr = Something > class MyClass {
    // as you had it, with no default constructor
};

// you want MyClass<T,true> to be just like MyClass<T,false>
// but with a default constructor:
template< typename T > class MyClass<T,true> : public MyClass<T,false> {

    MyClass() : MyClass<T,false>(/* chosen constructor args */) { etc; }

    using MyClass<T,false>::MyClass<T,false>;
};

if you don't have C++11 you can't use the using constructor inheritance and you'll have to redeclare all its constructors and forward their arguments along to the base class.

This is fingers-to-keyboard, I don't have a compiler handy atm so minor syntax goofs are somewhat likely.

Solvable answered 25/4, 2012 at 5:44 Comment(2)
@jhill Cannot make this compile in VS11. I have no support for constexpr.Artois
This does not compile: template <typename T, bool has_default_ctr> class MyClass { }; template <typename T, true> class MyClass: public MyClass<T, false> { MyClass() {} };Artois
E
0

You could do something as simple as

template < typename ...Whatever >
class MyClass
{
public:
    static constexpr bool has_default_ctr = Something;

    // I want this only if "has_default_ctr" is "true".
    MyClass()
    {
        static_assert(has_default_ctr, "Not Default Constructible");
    }

    //...
};
Extempore answered 25/4, 2012 at 20:0 Comment(0)
F
0

Here's the one I recently used. I needed a class template with a default constructor if its data members support a default constructor, otherwise the default constructor should not compile.

The class template defines a template type parameter, and the class defines a data member of such a type (a variant using inheritance might also be attractive, as it can use the empty baseclass optimization). The data member's default constructor is automatically called by the class's default constructor. If the data member's type doesn't have a default constructor, compilation fails. Otherwise it succeeds. Here is the code:

#include <string>
template <typename Data>
class Demo
{
    Data d_data;

    public:
        Demo() = default;        // OK when used and Data has a default
                                 // constructor. 

        Demo(Data const &data)   // Exampe of an additional constructor
        :
            d_data(data)
        {}

        // ...
};

struct WithoutDefault
{
    WithoutDefault(int)
    {}
};

int main()
{
    Demo<std::string> withString;
    // Demo<NoDefault> nope;              // won't compile

    WithoutDefault nod(10);
    Demo<WithoutDefault> nodefault(nod);  // OK
}
Fleischer answered 11/9, 2015 at 19:23 Comment(0)
T
0

With enable_if it's easy (it works if the class templated also):

#include <type_traits>

class MyClass
{
public:
    static constexpr bool has_default_ctr = Something;

    // I want this only if "has_default_ctr" is "true".
    template <typename = std::enable_if<has_default_ctr>::type>
    MyClass();

    //...
};
Trisaccharide answered 17/12, 2019 at 9:55 Comment(0)
P
0

Possible answer not exactly for this question . But if you want to enable default constructor for template class only when its template-parameter-type field has default constructor. Then you can just create explicitly defaulted constructor in template class. See example:

struct HasDefault
{
    HasDefault() = default;
    HasDefault(int) {}
};

struct NoDefault
{
    NoDefault() = delete;
    NoDefault(int) {}
};

template <typename ValueT>
struct Wrapper
{
    Wrapper() = default;
    Wrapper(ValueT &&value) : value(std::forward<ValueT>(value)) {}

    ValueT value;
};

int main()
{
    Wrapper<HasDefault> hasDefault;
    Wrapper<HasDefault> hasDefault2(1);
    //Wrapper<NoDefault> noDefault; error: use of deleted function 'Wrapper<ValueT>::Wrapper()
    Wrapper<NoDefault> noDefault2(1);
}
Pittel answered 28/4, 2020 at 15:28 Comment(0)
F
-1

you can use diferent constructors with differents arguments

MyClass(){

}
MyClass(int num){

}
MyClass(String s){
}

and the you can simple write and static function that return the class and writes the conditions inside:

static chooseContructor(/*parameters*/){
 if(/*something*/){
     return new MyCLass();
 }
 else if(/*something else*/){
     return new MyClass(int num);
 }
 else if{
    return new MyClass(String s);
 }
}

And so on... Something like that will give you a semi-automatic constructor chooser

Felicita answered 25/4, 2012 at 5:2 Comment(1)
Tho it fails if MyClass has members that are not default constructible.Fourfold

© 2022 - 2024 — McMap. All rights reserved.