How to use void_t like in is_default_constructible

Hello everyone.

I wanted to understand how is_default_constructible works, so with some research
I came up with this

1
2
3
4
5
6
template<typename T, typename = void>
struct is_default_constructible : std::false_type
{ };

template<typename T>
struct is_default_constructible<T, std::void_t<decltype(T())>> : std::true_type { };


I can't understand the role of void_t<> in this case.

1. Why do we put typename = void in the first definition of the struct?

2. In the specialization, why do we specialize like that? <T, void_t<decltype(T())>

3. Why is void_t used together with decltype?

In general: what is going on?!
Last edited on
Right now, this is incorrect:
- For array types of default-constructible elements with known bounds;
- For void.

1
2
3
4
5
static_assert(std::is_default_constructible<int[42]>());
static_assert( my::is_default_constructible<int[42]>()); // error

static_assert(!std::is_default_constructible<void>());
static_assert(! my::is_default_constructible<void>()); // error 

Is there anywhere the correct implementation of the standard is_default_constructible?

My goal is to understand how can templates look into classes and figure out things like if I've a default constructor, if my move constructor is noexcept...
The accepted answer in the following link should go some way to answering (at least part of) each of your queries in the OP: https://stackoverflow.com/questions/27687389/how-does-void-t-work
The boost implementation of is_default_constructible is here:
http://www.boost.org/doc/libs/master/boost/type_traits/is_default_constructible.hpp
You're trying to roll your own is_default_constructible as part of an exercise, or general learning, I suppose but there's std::is_default_constructible also available of course:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <type_traits>
#include <string>
#include <iomanip>

struct DefCon
{
    std::string m_str;
};
struct NonDefCon
{
    std::string m_str;
    NonDefCon (const std::string& str) : m_str(str){}
};

int main()
{
    std::cout << "DefCon is default constructible? : "
                    << std::boolalpha << std::is_default_constructible<DefCon> :: value << "\n";
    std::cout << "NonDefCon is default constructible? : "
                    << std::boolalpha << std::is_default_constructible<NonDefCon> :: value << "\n";
}
> but there's std::is_default_constructible also available of course:

There may be a cogent reason for rolling out a homegrown is_default_constructible

The problem with the mainstream implementations of std::is_default_constructible is that it ignores const qualifiers; for instance they would report that the type const int is default constructible.

Though, the standard does state these:
23.15.4.3 / Table 42
For template <class T> struct is_­default_­constructible; the condition is is_­constructible_­v<T> is true.

23.15.4.3/8:
The predicate condition for a template specialization is_­constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:
T t(declval<Args>()...);
http://eel.is/c++draft/meta.unary.prop


http://coliru.stacked-crooked.com/a/7441d021bca8d063
http://rextester.com/LBEHX27202
Checked this out with Cubbi; this was his response:
There are quite a few discrepancies between what the type traits check and what the similarly-named core language type requirements are. As far as I understand, the traits are what the library implementors find useful for the purposes of building generic libraries.

In the core language, default constructible means: can be constructed without an initialiser.

In the library, the concept DefaultConstructible means: can be constructed with an empty initialiser.

Cubbi has added this note to the cppreference page:
std::is_default_constructible<T> does not test that T x; would compile; it attempts direct-initialization with an empty argument list (see std::is_constructible). Thus, std::is_default_constructible_v<const int> and std::is_default_constructible_v<const int[10]> are true.
http://en.cppreference.com/w/cpp/types/is_default_constructible
Wow! Our little tete-a-tete reaches the hallowed portals of cppreference.com! Thank you JLBorges, Cubbi
As an aside, was aware any given compiler may not cover everything in the standard library and now learn that even the standard library may differ in subtle ways from the core language!
... ignores const qualifiers; for instance they would report that the type const int is default constructible ...

I'm wondering if it's const qualifiers or rather copy semantics that's behind the const int results: for e.g.
1
2
<< std::boolalpha << std::is_default_constructible<std::atomic<const int>>::value << " and " // false
              << std::is_constructible<std::atomic<const int>>::value << '\n' ; // false  

Similar for the DefCon type from the earlier post above:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <type_traits>
#include <string>
#include <iomanip>

struct DefCon
{
    std::string m_str;
    DefCon (const DefCon& ) = delete;
    DefCon& operator = (const DefCon&) = delete;
};

int main()
{
    std::cout << "DefCon is default constructible? : "
                    << std::boolalpha << std::is_default_constructible<const DefCon> :: value << "\n";
	//prints 'false' as program stands but 'true' if the deleted functions are removed
}

and might these, in turn, be in line with what Cubbi's saying?:
... attempts direct-initialization with an empty argument list ...
Last edited on
might these, in turn, be in line with what Cubbi's saying?:

If you look at the boost implementation linked earlier, is_default_constructible<T> tests whether "T()" compiles, except for when T is an array (in which case it tests the array element type) and when T is a reference type or a cv-void (in which case it hardcodes false)

So you can just compare your examples with T()'s behavior:

1
2
     using T = std::atomic<const int>;
     T(); // error 


1
2
3
4
5
6
7
8
9
10
11
struct DefCon
{
    std::string m_str;
    DefCon (const DefCon& ) = delete;
    DefCon& operator = (const DefCon&) = delete;
};

int main()
{
    DefCon(); // error
}


1
2
3
4
5
6
7
8
9
struct DefCon
{
    std::string m_str;
};

int main()
{
    DefCon(); // OK
}
Last edited on
Topic archived. No new replies allowed.