So I stumbled on this piece of code and I don't understand why the following
construct is not ill-formed.
template<typename T, std::size_t... lst>
struct mystruct : std::index_sequence<lst..., sizeof...(lst)> {
T i;
};
int main() {
mystruct<int> obj;
}
This should be ill-formed since the instantiation of mystruct with T = int will yield the following class(after T is substituted with int):
template<int, std::size_t... lst>
struct mystruct : std::index_sequence<, //Empty list expansion
0> {
int i;
};
How is the std::index_sequence<, 0> not ill-formed? This above compiles without an error.
An empty parameter pack is never "empty" in a way that would make a construct syntactically invalid.
[temp.variadic]
7 When N is zero, the instantiation of the expansion produces an
empty list. Such an instantiation does not alter the syntactic
interpretation of the enclosing construct, even in cases where
omitting the list entirely would otherwise be ill-formed or would
result in an ambiguity in the grammar. [ Example:
template<class... T> struct X : T... { };
template<class... T> void f(T... values) {
X<T...> x(values...);
}
template void f<>(); // OK: X<> has no base classes
// x is a variable of type X<> that is value-initialized
— end example ]
So it's not std::index_sequence<, 0>, but rather std::index_sequence<0>.
It is not illformed since it expands to std::index_sequence<0>. The parameter pack expansion works not just on a textual level but it also works with the intent of what is done. Otherwise, working with potentially empty parameter packs would be a real mess.
Related
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.
The following program:
#include <type_traits>
template<typename T, bool b>
struct S{
S() = default;
template<bool sfinae = true,
typename = std::enable_if_t<sfinae && !std::is_const<T>::value>>
operator S<T const, b>() { return S<T const, b>{}; }
};
template<typename T, bool b1, bool b2>
void f(S<const std::type_identity_t<T>, b1>,
// ^- T in non-deduced context for func-param #1
S<T, b2>)
// ^- T deduced from here
{}
int main() {
S<int, true> s1{};
S<int, false> s2{};
f(s1, s2);
}
is accepted by GCC (11.2) but rejected by Clang (13) and MSVC (19.latest), all for -std=c++20 / /std:c++20 (DEMO).
What compiler is correct here?
This is governed by [temp.deduct.call], particularly /4:
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference: [...]
In the OP's example, A is S<const int, true> and the (transformed) A is S<int, int>, and none of these [three] cases applies here, meaning deduction fails.
We may experiment with this by tweaking the program such that the deduced A vs tranformed A difference falls under one of the three cases; say [temp.deduct.call]/4.3
If P is a class and P has the form simple-template-id, then the transformed A can be a derived class D of the deduced A. [...]
#include <type_traits>
template<typename T, bool b>
struct S : S<const T, b> {};
template<typename T, bool b>
struct S<const T, b> {};
template<typename T, bool b1, bool b2>
void f(S<const std::type_identity_t<T>, b1>, S<T, b2>){}
int main() {
S<int, true> s1{};
S<int, true> s2{};
f(s1, s2);
}
This program is correctly accepted by all three compilers (DEMO).
Thus, GCC most likely has a bug here, as the error argued for above is diagnosable (not ill-formed NDR). As I could not find an open bug for the issues, I've filed:
Bug 103333 - [accepts-invalid] function template argument deduction for incompatible 'transformed A' / 'deduced A' pair
We may also note that [temp.arg.explicit]/7 contains a special case where implicit conversions are allowed to convert an argument type to the function parameter type:
Implicit conversions ([conv]) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction.
This does not apply in OP's example, though, as the (function) parameter type S<const std::type_identity_t<T>, b1> contains also the (non-type) template parameter b1, which participates in template argument deduction.
However in the following program:
#include <type_traits>
template<typename T>
struct S{
S() = default;
template<bool sfinae = true,
typename = std::enable_if_t<sfinae && !std::is_const<T>::value>>
operator S<T const>() { return S<T const>{}; }
};
template<typename T>
void f(S<const std::type_identity_t<T>>, S<T>) {}
int main() {
S<int> s1{};
S<int> s2{};
f(s1, s2);
}
the (function) parameter type is A<std::type_identity_t<T>>, and the only template parameter in it is T which does not participate in template argument deduction for that parameter-argument pair (P/A). Thus, [temp.arg.explicit]/7 do apply here and the program is well-formed.
When playing with libstdcxx's test_property:
template<template<typename...> class Property,
typename Type1, typename... Types>
constexpr bool
test_property(typename Property<Type1, Types...>::value_type value)
{
return (Property<Type1, Types...>::value == value
&& Property<Type1, Types...>::type::value == value);
}
class Property accepts at least 1 template parameter(Type1).
Here is a use case:
static_assert(test_property<is_copy_assignable, ExceptMoveAssignClass>(false), "");
But I found clang doesn't work fine with this function:
prog.cc:29:3: error: no matching function for call to 'test_property'
test_property<std::is_copy_assignable, DeletedMoveAssignClass>(false);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:12:1: note: candidate template ignored: substitution failure [with Property = std::is_copy_assignable, Type1 = DeletedMoveAssignClass]: too many template arguments for class template 'is_copy_assignable'
test_property(typename Property<Type1, Types...>::value_type value)
^ ~~~~~~~~
1 error generated.
The root cause is clang doesn't allow class Property to be class that only accepts one template parameter like template< class T > struct is_copy_assignable;. Once class Property is modified into Property<Type1>, it will compile successfully:
template<template<typename...> class Property, typename Type1>
constexpr bool
ya_test_property(typename Property<Type1>::value_type value)
{
return (Property<Type1>::value == value
&& Property<Type1>::type::value == value);
}
here is demo https://wandbox.org/permlink/LlL1o57Yted5WZo5
Of course, this function is from libstdcxx, so gcc can pass compile. Is this clang's bug?
Looks like a Clang bug if I'm interpreting [temp.variadic]/7 correctly:
When N is zero, the instantiation of the expansion produces an empty
list. Such an instantiation does not alter the syntactic
interpretation of the enclosing construct, even in cases where
omitting the list entirely would otherwise be ill-formed or would
result in an ambiguity in the grammar. [ Example:
template<class... T> struct X : T... { };
template<class... T> void f(T... values) {
X<T...> x(values...);
}
template void f<>(); // OK: X<> has no base classes
// x is a variable of type X<> that is value-initialized
— end example ]
Similarly, while std::is_copy_assignable<ExceptMoveAssignClass , > is ill-formed, an empty pack should not put us in this state. It should be equivalent to std::is_copy_assignable<ExceptMoveAssignClass>, which is well-formed.
Of course, if the pack wasn't empty, then we'd be passing too many arguments, which is ill-formed. But that is not the case.
In C++17 template arguments for a class template will be deduced more or less as it happens nowadays for a function template.
Here is the relevant paper.
As an example from the above mentioned paper:
template<class ... Ts> struct X { X(Ts...) };
X x1{1}; // OK X<int>
X x11; // OK X<>
Function templates have another interesting feature when deduction happens.
Consider the following code:
template<typename... U, typename... T>
auto func(T&&...) {}
// ...
// U is int, char - T is int, double
func<int, char>(0, .0);
We can have two parameters packs as long as deduction helps to discriminate between them.
No need to wrap them within a tuple or some other structure.
Will it be possible to do the same with class templates?
As an example:
template<typename... U, typename... T>
struct S {
S(T&&...) {}
};
// ...
// U is int, char - T is int, double
S<int, char> s{0, .0};
The paper contains the example below:
template<class ... Ts> struct X { X(Ts...) };
X<int> x3{1, 'a', "bc"}; // OK X<int,char,const char*>
Anyway, it isn't exactly the same thing and I'm not sure if it will be allowed or not.
This:
template<typename... U, typename... T>
struct S { ... };
is just ill-formed per [temp.param]:
If a template-parameter of a primary class template, primary variable template, or alias template is a template parameter pack, it shall be the last template-parameter.
This case:
template<class ... Ts> struct X { X(Ts...) };
X<int> x3{1, 'a', "bc"}; // OK X<int,char,const char*>
is problematic since X<int> is already a valid type. This part of the paper was dropped in Oulu, though it's possible some proposal in the future will make it possible to indicate that some of the class template parameters should be deduced but others should be specified:
X<string, ???> x3{"a", 1, "b"}; // X<string, int, const char*>.
where ??? is some series of tokens that makes intent clear.
I am trying to do a simple partial template specialization, but I get errors on g++4.4.7, g++4.8.5, clang++3.8.0. Whenever I mention compiler(s) error, I mean the output of all of these, as they always agree.
I am using C++03, compiling without any option.
The code:
#include <iostream>
template <typename T, typename X, typename G>
struct A {};
template <typename T, typename X>
struct A<T, X, void> { A() : n(1) {} X n; T b; };
template <typename X>
struct A<X, void, void> { A() : n(2) {} X n; };
int main() {
A<int, float> one;
A<int> two;
std::cout << one.n << " | " << two.n << "\n";
return 0;
}
Question 1: This code fails to compile. The compilers say that A<int, float> and A<int> are wrong as A requires 3 templates parameters. Why?
If I change the original declaration to
template <typename T, typename X = void, typename G = void>
struct A {};
The code compiles and the output is: 1 | 2.
What happens is that the compiler in a first step matches one and two type to the not specialized A, but then it correctly decides to use the code of the partially specialized class one would expect it to use. But it should not need the defaults.
I then decide to change the last partial specialization switching the first and second parameter:
template <typename X>
struct A<void, X, void> { A() : n(2) {} X n; };
I would expect this to change nothing, but the compilers disagree. The clearest output between the 3 is here reported:
a.cpp:7:40: error: field has incomplete type 'void'
struct A<T, X, void> { A() : n(1) {} X n; T b; };
^
a.cpp:14:10: note: in instantiation of template class 'A<int, void, void>' requested here
A<int> two;
^
1 error generated.
Question 2: Why are the compilers considering the two variable an instance of the partial specialization of A that specializes only one argument?
Note that is the "2nd matching", because if I only use 1 default template argument, the compiler will go back to complaining about the fact that 3 template parameters are needed.
Thanks.
Question 1: This code fails to compile. The compilers say that A<int, float> and A<int> are wrong as A requires 3 templates parameters. Why?
Because A requires 3 template parameters. You declared A as:
template <typename T, typename X, typename G>
struct A {};
There is no two- or one-template parameter version of A. There are versions specialized on some of the types being void, but that's still a parameter - not an absence of parameter.
When you add the defaults, then A<int, float> evaluates as A<int, float, void>, which is a valid instantiation - and picks the specialization which sets n to 1.
You're misunderstanding how specialization works. Specialization doesn't change the number of template parameters. It's just a way of adding special functionality depending on what the template parameters end up being.
Question 2: Why are the compilers considering the two variable an instance of the partial specialization of A that specializes only one argument?
We have three choices
template <T, X, G> struct A; // the primary
template <T, X, void> struct A; // (1)
template <void, X, void> struct A; // (2)
When we instantiate A<int>, that is the same as A<int, void, void> when we add in the default parameters. That does not match (2) - because that one requires the first parameter to be void and yours is int. (1) is a better match than the primary since it's more specialized. But then, (1) has a member of type X and in this case X is deduced as void (from the default parameter), and that's not allowed.