I have a class foo
with template parameter Tuple
and I want to provide a variadic arguments constructor to initialize a member variable m_elements
of type Tuple
, whenever the expression m_elements{ static_cast<typename Tuple::value_type>(std::forward<Elements>(elements))... }
is defined.
We can do this in the following way:
template<class Tuple>
struct foo
{
using value_type = typename Tuple::value_type;
template<class... Elements, class U = Tuple,
class = decltype(U{ static_cast<value_type>(std::declval<Elements>())... })>
foo(Elements&&... elements)
: m_elements{ static_cast<value_type>(std::forward<Elements>(elements))... }
{}
Tuple m_elements;
};
Now, whether this constructor is enabled or not should depend on some other conditions too. So, I would need to write something like
template<class... Elements, class U = Tuple,
class = std::enable_if_t</* some other conditions depending on U */>,
class = decltype(U{ static_cast<value_type>(std::declval<Elements>())... })>
I would like to check my first condition in terms of std::is_constructible
such that I could move this check into the enable_if
. Is this possible? I've tried to use std::is_constructible_v<U, decltype(static_cast<value_type>(std::declval<Elements>()))...>
, but this doesn't seem to be equivalent to the previous check.
For example, foo<bar<3>>{1, 2, 3};
with
template<std::size_t N>
struct bar
{
using value_type = double;
double data[N];
};
will compile with the previous check, but yields an error with the new one.
As Rostislav mentioned, if T
is not a function type, std::is_constructible_v<T, Args>
is true
iff the variable definition T obj(std::declval<Args>()...);
is well-formed. That's not the case in bar<1> obj(0.);
, cause bar<1>
has no corresponding constructor.
In contrast, bar1<1> obj{ 0. };
is well-formed. Using the proposed Detection Toolkit , we could use
template<class T, typename... Arguments>
using initializable_t = decltype(T{ std::declval<Arguments>()... });
template<class T, typename... Arguments>
constexpr bool is_initializable_v = is_detected_v<initializable_t, T, Arguments...>;
and change the check to
template<class... Elements, class U = Tuple,
class = std::enable_if_t<is_initializable_v<U, decltype(static_cast<value_type>(std::declval<Elements>()))...>>>
I think that's more readable than the plain decltype
approach.
The reason that the is_constructible
trait behaves differently is that there's indeed no constructor of bar
taking the three int
values as an argument. In this particular case, aggregate initialization is used.
But you could put the test into an enable_if
just by using a simple helper struct
:
#include <type_traits>
template<typename T>
struct test : std::true_type
{
};
template<class Tuple>
struct foo
{
using value_type = typename Tuple::value_type;
template<class... Elements, class U = Tuple,
class = std::enable_if_t<
test<decltype(U{ static_cast<value_type>(std::declval<Elements>())... })>::value>
/* && your other tests */
>
foo(Elements&&... elements)
: m_elements{ static_cast<value_type>(std::forward<Elements>(elements))... }
{}
Tuple m_elements;
};
template<std::size_t N>
struct bar
{
using value_type = double;
double data[N];
};
int main()
{
foo<bar<3>>{1, 2, 3};
}
Alternatively, you could push the decltype
magic to a separate place by creating a type trait using any of the answers to this question.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.