Is-braces-constructible type trait
template<class T, typename... Args>
decltype(void(T{std::declval<Args>()...}), std::true_type())
test(int);
template<class T, typename... Args>
std::false_type
test(...);
template<class T, typename... Args>
struct is_braces_constructible : decltype(test<T, Args...>(0))
{
};
Conditionally enable a constructor whenever a member variable can be constructed by variadic arguments
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.
Type traits in VS2013
This seems to be a known bug.
Link1: http://connect.microsoft.com/VisualStudio/feedback/details/800328/std-is-copy-constructible-is-broken
Link2: http://connect.microsoft.com/VisualStudio/feedback/details/802032/std-is-copy-constructible-doesnt-work-correctly
Arity of aggregate in logarithmic time
Discussion
(The discussion is based on another answer of mine which I will delete now.)
As in the original question, the following answer checks whether the invocation of the constructor of the aggregate is possible with a given number of arguments. For aggregates, one can base a binary search on this pattern by using the following properties from the standard:
8.5.1 (6):
An initializer-list is ill-formed if the number of initializer-clauses
exceeds the number of members or elements to initialize. [ Example:
char cv[4] = { ’a’, ’s’, ’d’, ’f’, 0 }; // error is ill-formed. — end
example ]
and
8.5.1 (7):
If there are fewer initializer-clauses in the list than there are
members in the aggregate, then each member not explicitly initialized
shall be initialized from its default member initializer (9.2) or, if
there is no default member initializer, from an empty initializer list
(8.5.4). [ Example: struct S { int a; const char* b; int c; int d =
b[a]; }; S ss = { 1, "asdf" }; initializes ss.a with 1, ss.b with
"asdf", ss.c with the value of an expression of the form int{} (that
is, 0), and ss.d with the value of ss.b[ss.a] (that is, ’s’), and in
struct X { int i, j, k = 42; }; X a[] = { 1, 2, 3, 4, 5, 6 }; X b[2] =
{ { 1, 2, 3 }, { 4, 5, 6 } }; a and b have the same value — end
example ]
However, as you already implied by the question title, a binary search will in general not work with non-aggregates, first due to the fact that those are usually not callable with less parameters than necessary, and next due to the fact that non-aggregates can have explicit
constructors so that the "conversion-to-anything" trick via the struct filler
won't work.
Implementation
First ingredient is an is_callable
check from here:
template<typename V, typename ... Args>
struct is_constructible_impl
{
template<typename C> static constexpr auto test(int) -> decltype(C{std::declval<Args>() ...}, bool{}) { return true; }
template<typename> static constexpr auto test(...) { return false; }
static constexpr bool value = test<V>(int{});
using type = std::integral_constant<bool, value>;
};
template<typename ... Args>
using is_constructible = typename is_callable_impl<Args...>::type;
Note that this one is usable also with a fewer number of parameters than necessary (unlike your check).
Next a helper function which takes an integer argument and returns whether the aggregate is callable with the corresponding number of constructor arguments:
template<typename A, size_t ... I>
constexpr auto check_impl(std::index_sequence<I ...>)
{
return is_constructible<A, decltype(I, filler{}) ...>::value;
}
template<typename A, size_t N>
constexpr auto check()
{
return check_impl<A>(std::make_index_sequence<N>{});
}
And finally the binary search:
template<typename A, size_t Low, size_t Up, size_t i = Low + (Up - Low)/2>
struct binary_search
: public std::conditional_t<check<A, i>() && !check<A,i+1>()
, std::integral_constant<size_t, i>
, std::conditional_t<check<A, i>()
, binary_search<A, i, Up>
, binary_search<A, Low, i> >
>
{};
Use it as
int main()
{
static_assert(binary_search<A2,0,10>::value==2);
}
Live on Coliru
(Im)perfect forwarding with variadic templates
Here are the different ways to write a properly constrained constructor template, in increasing order of complexity and corresponding increasing order of feature-richness and decreasing order of number of gotchas.
This particular form of EnableIf will be used but this is an implementation detail that doesn't change the essence of the techniques that are outlined here. It's also assumed that there are And
and Not
aliases to combine different metacomputations. E.g. And<std::is_integral<T>, Not<is_const<T>>>
is more convenient than std::integral_constant<bool, std::is_integral<T>::value && !is_const<T>::value>
.
I don't recommend any particular strategy, because any constraint is much, much better than no constraint at all when it comes to constructor templates. If possible, avoid the first two techniques which have very obvious drawbacks -- the rest are elaborations on the same theme.
Constrain on self
template<typename T>
using Unqualified = typename std::remove_cv<
typename std::remove_reference<T>::type
>::type;
struct foo {
template<
typename... Args
, EnableIf<
Not<std::is_same<foo, Unqualified<Args>>...>
>...
>
foo(Args&&... args);
};
Benefit: avoids the constructor from participating in overload resolution in the following scenario:
foo f;
foo g = f; // typical copy constructor taking foo const& is not preferred!
Drawback: participates in every other kind of overload resolution
Constrain on construction expression
Since the constructor has the moral effects of constructing a foo_impl
from Args
, it seems natural to express the constraints on those exact terms:
template<
typename... Args
, EnableIf<
std::is_constructible<foo_impl, Args...>
>...
>
foo(Args&&... args);
Benefit: This is now officially a constrained template, since it only participates in overload resolution if some semantic condition is met.
Drawback: Is the following valid?
// function declaration
void fun(foo f);
fun(42);
If, for instance, foo_impl
is std::vector<double>
, then yes, the code is valid. Because std::vector<double> v(42);
is a valid way to construct a vector of such type, then it is valid to convert from int
to foo
. In other words, std::is_convertible<T, foo>::value == std::is_constructible<foo_impl, T>::value
, putting aside the matter of other constructors for foo
(mind the swapped order of parameters -- it is unfortunate).
Constrain on construction expression, explicitly
Naturally, the following comes immediately to mind:
template<
typename... Args
, EnableIf<
std::is_constructible<foo_impl, Args...>
>...
>
explicit foo(Args&&... args);
A second attempt that marks the constructor explicit
.
Benefit: Avoids the above drawback! And it doesn't take much either -- as long as you don't forget that explicit
.
Drawbacks: If foo_impl
is std::string
, then the following may be inconvenient:
void fun(foo f);
// No:
// fun("hello");
fun(foo { "hello" });
It depends on whether foo
is for instance meant to be a thin wrapper around foo_impl
. Here is what I think is a more annoying drawback, assuming foo_impl
is std::pair<int, double*>
.
foo make_foo()
{
// No:
// return { 42, nullptr };
return foo { 42, nullptr };
}
I don't feel like explicit
actually saves me from anything here: there are two arguments in the braces so it's obviously not a conversion, and the type foo
already appears in the signature, so I'd like to spare with it when I feel it is redundant. std::tuple
suffers from that problem (although factories like std::make_tuple
do ease that pain a bit).
Separately constrain conversion from construction
Let's separately express construction and conversion constraints:
// New trait that describes e.g.
// []() -> T { return { std::declval<Args>()... }; }
template<typename T, typename... Args>
struct is_perfectly_convertible_from: std::is_constructible<T, Args...> {};
template<typename T, typename U>
struct is_perfectly_convertible_from: std::is_convertible<U, T> {};
// New constructible trait that will take care that as a constraint it
// doesn't overlap with the trait above for the purposes of SFINAE
template<typename T, typename U>
struct is_perfectly_constructible
: And<
std::is_constructible<T, U>
, Not<std::is_convertible<U, T>>
> {};
Usage:
struct foo {
// General constructor
template<
typename... Args
, EnableIf< is_perfectly_convertible_from<foo_impl, Args...> >...
>
foo(Args&&... args);
// Special unary, non-convertible case
template<
typename Arg
, EnableIf< is_perfectly_constructible<foo_impl, Arg> >...
>
explicit foo(Arg&& arg);
};
Benefit: Construction and conversion of foo_impl
are now necessary and sufficient conditions for construction and conversion of foo
. That is to say, std::is_convertible<T, foo>::value == std::is_convertible<T, foo_impl>::value
and std::is_constructible<foo, Ts...>::value == std::is_constructible<foo_impl, T>::value
both hold (almost).
Drawback? foo f { 0, 1, 2, 3, 4 };
doesn't work if foo_impl
is e.g. std::vector<int>
, because the constraint is in terms of a construction of the style std::vector<int> v(0, 1, 2, 3, 4);
. It is possible to add a further overload taking std::initializer_list<T>
that is constrained on std::is_convertible<std::initializer_list<T>, foo_impl>
(left as an exercise to the reader), or even an overload taking std::initializer_list<T>, Ts&&...
(constraint also left as an exercise to the reader -- but remember that 'conversion' from more than one argument is not a construction!). Note that we don't need to modify is_perfectly_convertible_from
to avoid overlap.
The more obsequious amongst us will also make sure to discriminate narrow conversions against the other kind of conversions.
Constraint on std::optional's forwarding reference constructor
Lying traits are ungood.
Lying traits for a fundamental vocabulary type are plusungood.
Lying traits for a fundamental vocabulary type that can also easily interfere with overload resolution are doubleplusungood.
void f(std::optional<int>);
void f(std::optional<const char*>);
f({""}); // ambiguous without the constraint
Related Topics
Member Fields, Order of Construction
How to Restart My Own Qt Application
Maximum Stack Size for C/C+ Program
What Should Std::Vector::Data() Return If the Vector Is Empty
Disable Eclipse's Error Discovery. (Codan False Positives)
Why Are Forward Declarations Necessary
Is There a Limit of Stack Size of a Process in Linux
Printf Rounding Behavior for Doubles
Write and Read String to Binary File C++
Boost::Spirit Expression Parser
Why Does My Program Hang When Opening a Mkfifo-Ed Pipe
How to Use Boost Preprocessor to Generate Accessors
Set All Bytes of Int to (Unsigned Char)0, Guaranteed to Represent Zero
C++ Error: Undefined Reference to 'Clock_Gettime' and 'Clock_Settime'
Command Working in Terminal, But Not via Qprocess
Differencebetween Cout, Cerr, Clog of iOStream Header in C++? When to Use Which One