If your constructor's signature is far more permissive than it should be, that's the issue - not is_constructible
's implementation. In your original example,
template <typename... Ts, typename=decltype(base{std::declval<Ts>()...})>
aggregate_wrapper(Ts&&... xs)
: base{std::forward<Ts>(xs)...} {/*…*/}
does the job. If is_constructible
"spuriously" gives a green light, so might your constructor template spuriously be selected over other constructors, because overload resolution finds it to be the optimal match.
However, overload resolution isn't designed to give only true negatives/positives: It is designed to find the best match given appropriate arguments, or yield nothing if the arguments are sufficiently inappropriate. is_constructible
may be superficial in some sense, but that's what traits are for - checking signatures, which are an entity's representation in the realms of overload resolution and SFINAE - not preventing that your function templates accept everything but only actually instantiate soundly for a small margin of arguments.
That's your responsibility, and if you meet it, you will both get proper results from is_constructible
and efficient compilation. Which is definitely better than unfailing operation of is_constructible
and high compilation times with multitudinous, hidden rules in calls to function templates.