3

My question is an extension of this question: How to use sfinae for selecting constructors?

In the previous question, the asker just wanted to selectively enable a single constructor. I would like to change the behaviour of the constructor depending on whether the class template parameter type is default-constructible - the best way that I can come up with to do this is to have two constructors with the same usage such that exactly one is enabled for every instantiation. My case is also different because neither version of the constructor would be a template function if I weren't trying to selectively enable with enable_if (whereas in the linked question both versions of the constructor are templated on int otherN).

The comments in the accepted answer to the above question led me to this site, which led me to create the following minimal example:

#include <iostream>
#include <type_traits>

namespace detail {
    enum class enabler {};
    enum class disabler {};
}

template <typename Condition>
using EnableIf = typename std::enable_if<Condition::value, detail::enabler>::type;

template <typename Condition>
using DisableIf = typename std::enable_if<!Condition::value, detail::disabler>::type;

template<typename T>
struct A {

    T data;

    // Valid if T is default-construtible; SFINAE otherwise
    template<EnableIf<std::is_default_constructible<T>>...>
    A() { std::cout << "Data defaulted" << std::endl; }


    // Valid if T is *not* default-constructible; SFINAE otherwise
    template<DisableIf<std::is_default_constructible<T>>...>
    A() : data(0) { std::cout << "Data zeroed" << std::endl; }
};

// struct which is not default-constructible
struct B {
    B() = delete;
    B(int) {}
};

int main()
{
    A<int> x; // int is default-constructible
    A<B> y; // class B is not default-constructible

    return 0;
}

I can compile this (with -std=c++11) if I comment out the first constructor and the declaration of x or the second constructor and the declaration of y. I would like to do neither, but when I try that the compiler complains that there is no type named type in std::enable_if<false, >.

The answers to this question take another approach to a similar problem, but I don't understand the factors at play well enough to be able to combine the approaches into something which works.

Community
  • 1
  • 1
user1476176
  • 1,045
  • 1
  • 7
  • 15
  • [almost-static-if](https://rmf.io/cxx11/almost-static-if) link needs updating. (no trailing slash) – tukra Dec 30 '16 at 20:50

3 Answers3

5

Not ideal, but this gets the job done:

#include <iostream>
#include <type_traits>

template<typename T>
struct A {

    T data;

    A() : A((std::is_default_constructible<T> *)nullptr) {}

private:
    A(std::true_type *) { std::cout << "Data defaulted" << std::endl; }

    A(std::false_type *) : data(0) { std::cout << "Data zeroed" << std::endl; }
};

// struct which is not default-constructible
struct B {
    B() = delete;
    B(int) {}
};

int main()
{
    A<int> x; // int is default-constructible
    A<B> y; // class B is not default-constructible

    return 0;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 2
    Removing the pointers and passing `std::is_default_constructible::type` instead makes it easier to read imo ... otherwise +1. – davidhigh Jul 23 '16 at 10:20
5

Aside from @Sam's tag dispatch solution you can use std::enable_if on the constructors, but you have to be aware of the following:

  1. the template parameter used for the std::is_default_constructible cannot be T, instead you need a "new" template parameter (which can be defaulted to T). See this SO question/answer for details: std::enable_if to conditionally compile a member function

  2. Quoting from cppreference.com:

A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal.

This leads to the following code:

#include <iostream>
#include <type_traits>

template<typename T>
struct A {

    T data;

    // Valid if T is default-constructible; SFINAE otherwise
    template<typename X = T, typename SFINAE = typename std::enable_if<std::is_default_constructible<X>::value>::type, typename P = SFINAE>
    A() { std::cout << "Data defaulted" << std::endl; }


    // Valid if T is *not* default-constructible; SFINAE otherwise
    template<typename X = T, typename = typename std::enable_if<!std::is_default_constructible<X>::value>::type>
    A() : data(0) { std::cout << "Data zeroed" << std::endl; }
};

// struct which is not default-constructible
struct B {
    B() = delete;
    B(int) {}
};

int main()
{
    A<int> x; // int is default-constructible
    A<B> y; // class B is not default-constructible

    return 0;
}

live example

Community
  • 1
  • 1
m.s.
  • 16,063
  • 7
  • 53
  • 88
  • I notice that you used `-std=c++14` in your live example, and also that this doesn't compile when I use `-std=c++11`. What change makes this possible in 14 but not 11? – user1476176 Jul 23 '16 at 15:51
  • @user1476176 I updated the code to be C++11 compatible; the only change was to use `std::enable_if` instead of `std::enable_if_t` – m.s. Jul 23 '16 at 15:53
0

You could also create two alternative class implementations depending on the T and its default constructable ability:

#include <type_traits>
#include <iostream>

template <class T, class = void>
struct A_parent;

template <class T>
struct A_parent<T, typename std::enable_if<std::is_default_constructible<T>::value>::type> {
   T data;
   A_parent() {  std::cout << "Default constructable" << std::endl; }
};

template <class T>
struct A_parent<T, typename std::enable_if<!std::is_default_constructible<T>::value>::type> {
   T data;
   A_parent(): data(0) { std::cout << "Not default constructable" << std::endl; }
};

template <class T>
struct A: A_parent<T> {
   /* further implementation */
};

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

int main() {
   A<int> a1;
   A<B> a2;
}

Output:

 Default constructable 
 Not default constructable
W.F.
  • 13,888
  • 2
  • 34
  • 81