How to use SFINAE to select constructor from multiple options in C++11

限于喜欢 提交于 2019-12-07 18:36:37

问题


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.


回答1:


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



回答2:


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




回答3:


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


来源:https://stackoverflow.com/questions/38537155/how-to-use-sfinae-to-select-constructor-from-multiple-options-in-c11

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!