Why is the following code behaving as commented?
struct S
{
template<typename T, typename = std::enable_if_t<!std::is_constructible_v<S, T>>>
S(T&&){}
};
int main() {
S s1{1}; // OK
int i = 1;
S s2{i}; // OK
static_assert(std::is_constructible_v<S, int>); // ERROR (any compiler)
}
I get that for the constructor to be enabled, the assertion must be false. But S still is constructed from int in the example above! What does the standard say and what does the compiler do?
I would assume that before enabling the template constructor, S in not constructible so std::is_constructible<S, int> instantiates to false. That enables the template constructor but also condemns std::is_constructible<S, int> to always test false.
I've also tested with my own (pseudo?) version of std::is_constructible:
#include <type_traits>
template<typename, typename T, typename... ARGS>
constexpr bool isConstructibleImpl = false;
template<typename T, typename... ARGS>
constexpr bool isConstructibleImpl<
std::void_t<decltype(T(std::declval<ARGS>()...))>,
T, ARGS...> =
true;
template<typename T, typename... ARGS>
constexpr bool isConstructible_v = isConstructibleImpl<void, T, ARGS...>;
struct S
{
template<typename T, typename = std::enable_if_t<!isConstructible_v<S, T>>>
S(T&&){}
};
int main() {
S s1{1}; // OK
int i = 1;
S s2{i}; // OK
static_assert(std::is_constructible_v<S, int>); // OK
}
I suppose that it is because now std::is_constructible is not sacrificed for SFINAE in the constructor. isConstructible is sacrificed instead.
That brings me to a second question: Is this last example a good way to perform SFINAE on a constructor without corrupting std::is_constructible?
Rational: I ended up trying that SFINAE pattern to tell the compiler not to use the template constructor if any of the other available constructors matches (especially the default ones), even imperfectly (e.g., a const & parameter should match a & argument and the template constructor should not be considered a better match).
Your first code example is undefined behaviour, because S is not a complete type within the declaration of itself. std::is_constructible_v however requires all involved types to be complete:
See these paragraphs from cppreference.com:
T and all types in the parameter pack Args shall each be a complete
type, (possibly cv-qualified) void, or an array of unknown bound.
Otherwise, the behavior is undefined.
If an instantiation of a template above depends, directly or
indirectly, on an incomplete type, and that instantiation could yield
a different result if that type were hypothetically completed, the
behavior is undefined.
This makes sense because in order for the compiler to know if some type can be constructed it needs to know the full definition. In your first example, the code is kind of recursive: the compiler needs to find out if S can be constructed from T by checking the constructors of S which themselves depend on is_constructible etc.
Related
This question is due to insane curiosity rather than an actual problem.
Consider the following code:
template<typename...>
struct type_list {};
template<typename, typename = void>
struct test_class;
template<typename... T>
struct test_class<type_list<T...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
int main() {
static_assert(!test_class<type_list<double, char>>::value);
static_assert(test_class<type_list<int>>::value);
}
This fails with the error:
ambiguous partial specializations of 'test_class<type_list>'
If I changed the second specialization to something that doesn't work from a functional point of view, the error would go away:
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = true;
};
Similarly, if I use the alias template void_t, everything works as expected:
template<typename T>
struct test_class<type_list<T>, std::void_t<std::enable_if_t<std::is_same_v<T, int>>>> {
static constexpr auto value = true;
};
Apart from the ugliness of combining void_t and enable_if_t, this also gets the job done when there is a single type that differs from int, ie for a static_assert(!test_class<type_list<char>>::value) (it wouldn't work in the second case instead, for obvious reasons).
I see why the third case works-ish, since the alias template is literally replaced with void when the condition of the enable_if_t is satisfied and type_list<T> is more specialized than type_list<T...> (right?).
However, I would have expected the same also for the following:
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
At the end of the day, std::enable_if_t<std::is_same_v<T, int>> somehow is void when the condition is statisfied (ok, technically speaking it's typename blabla::type, granted but isn't ::type still void?) and therefore I don't see why it results in an ambiguous call. I'm pretty sure I'm missing something obvious here though and I'm curious to understand it now.
I'd be glad if you could point out the standardese for this and let me know if there exists a nicer solution than combining void_t and enable_if_t eventually.
Let's start with an extended version of your code
template<typename, typename = void>
struct test_class;
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = false;
};
template<typename... Ts>
struct test_class<type_list<Ts...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
that is called with
test_class<type_list<int>>::value
Try it here!
The standard distinguishes between template parameters that are equivalent, ones that are only functionally equivalent and others which are not equivalent [temp.over.link]/5
Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression.
Two unevaluated operands that do not involve template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name types and declarations may differ as long as they name the same entities, and the tokens used to form concept-ids may differ as long as the two template-ids are the same ([temp.type]).
Two potentially-evaluated expressions involving template parameters that are not equivalent are functionally equivalent if, for any given set of template arguments, the evaluation of the expression results in the same value.
Two unevaluated operands that are not equivalent are functionally equivalent if, for any given set of template arguments, the expressions perform the same operations in the same order with the same entities.
E.g. std::enable_if_t<std::is_same_v<T, T>> and void are only functionally equivalent: The first term will be evaluated to void for any template argument T. This means according to [temp.over.link]/7 code containing two specialisations <T, void> and <T, std::enable_if_t<std::is_same_v<T, T>> is already ill-formed:
If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.
In the code above std::enable_if_t<std::is_same_v<T, int>> is not even functionally equivalent to any of the other versions as it is in general not equivalent to void.
When now performing partial ordering [temp.func.order] to see which specialisation matches best your call this will result in an ambiguity as test_class is equally specialised [temp.func.order]/6 in both cases (with either Ts={int}, void or T=int, std::enable_if_t<std::is_same_v<T, int>>, both resulting in int, void but you can't deduce them against each other) and therefore the compilation will fail.
On the other hand by wrapping std::enable_if_t with std::void_t, which is nothing more but an alias for void
template <typename T>
using void_t = void;
the partial ordering will succeed as in this case the compiler will already know the type of the last template parameter is void in all cases, choosing test_class<T, std::void_t<std::enable_if_t<std::is_same_v<T,int>>>> with T=int as the most specialised as the non-variadic case (T=int, void) is considered more specialised than the variadic one Ts={int}, void (see e.g. temp.deduct.partial/8 or again this).
I want to define a generic strong alias type, i.e. a type
template<typename T, auto ID = 0>
class StrongAlias {
T value;
};
such that for a type T a StrongAlias<T> can be used in exactly the same way as T, but StrongAlias<T, 0> and StrongAlias<T, 1> are different types that can not be implecitly converted to each other.
In order to mimic a T as perfectly as possible, I would like my StrongAlias to have the same constructors as T.
This means I would like to do something like the following:
template<typename T, auto ID = 0>
class StrongAlias {
T value;
public:
// doesn't work
template<typename... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
StrongAlias(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
: value(std::forward<Args>(args)...) {}
};
except that this wouldn't work since the template parameter pack must be the last template parameter, as clang 5.0 would tell me.
The other way to use SFINAE that I thought of would be in the return type, but since a constructor doesn't have a return type, this does not seem to work either.
Is there any way to use SFINAE on a variadic template parameter pack in a constructor?
Alternatively, if there isn't one, can I accomplish what I want in another way?
Note that being implicitly constructible from a T isn't enough in my case, as the example of StrongAlias<std::optional<int>> shows: If StrongAlias can only be implictly constructed from a std::optional<int>, it cannot be be constructed from a std::nullopt (of type std::nullopt_t), because that would involve 2 user-defined conversions. I really want to have all constructors of the aliased type.
EDIT:
Of course it would be possible to implement this without SFINAE and let the program be invalid if a StrongAlias is constructed from incompatible arguments. However, while this would be an acceptable behaviour in my specific case, it is clearly not optimal as the StrongAlias may be used in a template that queries if the given type is constructible from some arguments (via std::is_constructible). While this would yield a std::false_type for T, it would result in a std::true_type for StrongAlias<T>, which could mean unnecessary compile errors for StrongAlias<T> that wouldn't exist for T.
Just change std::enable_if_t to a non-type template parameter:
template<typename T, auto ID = 0>
class StrongAlias {
T value;
public:
template<typename... Args, std::enable_if_t<std::is_constructible_v<T, Args...>, int> = 0>
StrongAlias(Args&&... args) noexcept(noexcept(T(std::declval<Args>()...)))
: value(std::forward<Args>(args)...) {}
};
The two issues with your snippet that make it not compile are
Trying to pass a type std::is_constructible<T, Args...> as the first argument of std::enable_if_t;
Trying to pass decltype(...) to a noexcept operator.
(There's also a third problem with the function style cast inside the noexcept, but that only affects semantics, not compilability.)
Neither causes the error message you cite, which concerns a rule that doesn't apply to function templates at all. With these two problems fixed, Wandbox's Clang 5.0 happily accepts it.
I was looking through the documentation of SFINAE and there was this template declaration:
template<typename SomeType>
struct inner_type { typedef typename SomeType::type type; };
template <
class T,
class = typename T::type, // SFINAE failure if T has no member type
class U = typename inner_type<T>::type // hard error if T has no member type
// (guaranteed to not occur as of C++14)
> void foo(int) {}
Specifically, I'm asking about class = typename T::type. What's the point of declaring an unnamed class?
Because of the comment I thought that this will result in a compiler error when T doesn't have a member type, but that isn't the case, as foo<int, int, int>(0); compiles fine.
On the other hand
template<class T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
void foo(T t) {}
doesn't compile if T is signed, and compiles if T is unsigned.
What am I missing here?
foo<int, int, int>(0); compiles fine.
Because you specify the 2nd template argument, then the default template argument (i.e. typename T::type) won't be used, then won't trigger compile error.
If you just write foo<int>(0); to make the default template argument to be used, compile will fail.
LIVE
And it's same for your 2nd sample too.
What's the point of declaring an unnamed class?
Because the template parameter won't be used for the template implementation.
How can we implement a variadic template that, given a type T and a list of types E1, E2, ... EN, determines the type of that list for which the conversion from T to that type is, according to overload resolution, the best?
void should be the output if no best conversion exists - in other words, when either there is an ambiguity or T cannot be converted to any type in the list. Note that this implies that our template should be SFINAE-friendly, i.e. not hard-error when no best conversion exists.
The following static_asserts should succeed:
static_assert( std::is_same< best<int, long, short>, void >{}, "" );
static_assert( std::is_same< best<int, long, std::string>, long >{}, "" );
static_assert( std::is_same< best<int>, void >{}, "" );
(Assuming, for simplicity, that best is an alias template referring to the actual template)
This case is left unspecified:
static_assert( std::is_same< best<int, int, int>, ???>{}, "" );
Either void or int should be acceptable here. (If the latter is chosen then we can still check in a wrapper template whether the result type is contained twice in the list, and if it is, output void instead).
My currently best approach:
#include <type_traits>
template <class T> using eval = typename T::type;
template <class T> struct identity {using type = T;};
template <typename T, typename... E>
class best_conversion
{
template <typename...> struct overloads {};
template <typename U, typename... Rest>
struct overloads<U, Rest...> :
overloads<Rest...>
{
using overloads<Rest...>::call;
static identity<U> call(U);
};
template <typename U>
struct overloads<U>
{
static identity<U> call(U);
};
template <typename... E_>
static identity<eval<decltype(overloads<E_...>::call(std::declval<T>()))>>
best_conv(int);
template <typename...>
static identity<void> best_conv(...);
public:
using type = eval<decltype(best_conv<E...>(0))>;
};
template <typename... T>
using best_conversion_t = eval<best_conversion<T...>>;
Demo. For the "unspecified" case above this template will give you int.
The basic idea is to put a bunch of overloaded functions with one parameter into different scopes that name lookup will look in, with the parameter and return type of each overload corresponding to one of the types in our list.
overloads does this job by recursively introducing one declaration of call at a time and adapting all previously introduced calls from the base specializations via using declarations. That way all calls are in different scopes but are considered equally when it comes to overload resolution.
Then apply SFINAE in a function template best_conv to check whether the call to call (inside overloads) is well-formed: If it is, take the return type (which is by definition the parameter type) of the selected declaration and use it as our result - it will be the type we are looking for. Also provide a second overload of best_conv that returns void and can be selected as a default (when SFINAE applies in the first overload and kicks it out of the candidate set).
The return types use identity<> to avoid type decays when working with e.g. array or function pointer types.
Consider the following code, which tries to determine existence of a nested typedef.
#include<type_traits>
struct foo;// incomplete type
template<class T>
struct seq
{
using value_type = T;
};
struct no_type{};
template<class T>
struct check_type : std::true_type{};
template<>
struct check_type<no_type> :std::false_type{};
template<class T>
struct has_value_type
{
template<class U>
static auto check(U const&)-> typename U:: value_type;
static auto check(...)->no_type;
static bool const value = check_type<decltype(check(std::declval<T>()))>::value;
using type = has_value_type;
};
int main()
{
char c[has_value_type<seq<foo>>::value?1:-1];
(void)c;
}
Now invoking has_value_type<seq>::value causes compilation error as invalid use of incomplete type seq<foo>::value_type.
does decltype needs a complete type in the expression? If not, how can I remove the error? I am using gcc 4.7 for compilation.
Your code is valid C++11, which defines that a toplevel function call that appears as a decltype operand does not introduce a temporary even when the call is a prvalue.
This rule specifically was added to make code as yours valid and to prevent instantiations of the return type (if it is a class template specialization) otherwise needed to determine the access restrictions of a destructor.
decltype requires a valid expression, and you certainly can have a valid expression that involves incomplete types. The problem in your case however is
template<class U>
auto check(U const&) -> typename U::value_type;
which has return type foo when U is seq<foo>. You can't return an incomplete type by value, so you end up with an ill-formed expression. You can use a return type of e.g. void_<typename U::value_type> (with template<typename T> struct void_ {};) and your test appears to work.