For a bit of fun, I created a very basic compile-time type-value map class, as follows:
template <typename T, auto V>
struct TypeValuePair { };
template <typename... TypeValuePairs>
struct TypeValueMap
{
struct MapItems : TypeValuePairs... { };
template <typename T, auto V>
static constexpr auto Lookup(TypeValuePair<T, V>*)
{ return V; }
template <auto V, typename T>
static T Lookup(TypeValuePair<T, V>*);
template <typename T>
static constexpr auto ValueFor = Lookup<T>((MapItems*)nullptr);
template <auto V>
using TypeFor = decltype(Lookup<V>((MapItems*)nullptr));
};
to be used in a way such as this:
struct A; struct B; struct C;
enum class Values { A, B, C };
using Map = TypeValueMap<
TypeValuePair<A, Values::A>,
TypeValuePair<B, Values::B>,
TypeValuePair<C, Values::C>,
TypeValuePair<struct Other, 0>
>;
static_assert(Map::ValueFor<A> == Values::A, "");
static_assert(Map::ValueFor<B> == Values::B, "");
static_assert(Map::ValueFor<C> == Values::C, "");
static_assert(Map::ValueFor<struct Other> == 0, "");
static_assert(std::is_same<Map::TypeFor<Values::A>, A>::value, ""); //***
static_assert(std::is_same<Map::TypeFor<Values::B>, B>::value, "");
static_assert(std::is_same<Map::TypeFor<Values::C>, C>::value, "");
static_assert(std::is_same<Map::TypeFor<0>, struct Other>::value, ""); //***
Unfortunately, the two lines marked //*** fail with the error failed template argument deduction or similar on clang and g++ (the two compilers I have to hand). I can understand why this might be because Values::A has the value 0 so the two potentially collide. However, I would argue that they are in fact different types – one is plain integer, the other an enum class with underlying type integer – and so shouldn't in fact collide.
If I implement my map class differently, like so:
template <typename T, auto V>
struct TypeValuePair
{
protected:
static constexpr auto Lookup(T*)
{ return V; }
template <template <auto> class Wrapper>
static T Lookup(Wrapper<V>*);
};
template <typename... TypeValuePairs>
struct TypeValueMap
{
struct MapItems : TypeValuePairs...
{ using TypeValuePairs::Lookup...; };
template <auto> struct LookupByValue;
template <typename T>
static constexpr auto ValueFor = MapItems::Lookup((T*)nullptr);
template <auto V>
using TypeFor = decltype(MapItems::Lookup((LookupByValue<V>*)nullptr));
};
then there are no template argument deduction errors.
Therefore the question is, is the failure to deduce the template argument in the first implementation due to a bug in the compilers (given my assertion that integer and enum class should be treated as different types and not collide) or is it a misunderstanding on my side of what is possible with template argument deduction (I am not a language lawyer!), or some other bug in my implementation?
The problem is that Values::A is convertible to 0 by means of a "converted constant expression".
From the C++14 standard (n4140 section 5.19, paragraph 3, page 133):
A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, where the converted expression is a core constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions (8.5.4).
[ Note: such expressions may be used in new expressions (5.3.4), as case expressions (6.4.2), as enumerator initializers if the underlying type is fixed (7.2), as array bounds (8.3.4), and as integral or enumeration non-type template arguments (14.3). —end note ]
(my emphasis)
The effect is that there is an overload ambiguity between 0 and Values::A.
Related
The code is as follows.
#include <tuple>
#include <array>
template <typename T, typename Type>
struct Vec {
using value_type = T;
static constexpr size_t size() { return Type::size; }
};
template <size_t Size>
struct Const {
static constexpr size_t size = Size;
};
template <class T, class Type, class = void>
struct vec_size_impl {};
template <class T, class Type>
struct vec_size_impl<T, Type, std::enable_if_t<std::is_arithmetic_v<T>>>
: std::integral_constant<size_t, Type::size> {};
template <class T, class Type>
inline constexpr size_t Vec_size_v = vec_size_impl<T, Type>::value;
template<class T, class Type, size_t... Sizes>
// template<size_t... Sizes, class T, class Type>
std::enable_if_t<
((0 + ... + Sizes) == Vec_size_v<T, Type>),
std::tuple<Vec<T, Const<Sizes>>...>
> split(const Vec<T, Type>&) noexcept {
return std::make_tuple<Vec<T, Const<Sizes>>...>();
}
template<class V, class Type>
std::enable_if_t<
(Vec_size_v<typename V::value_type, Type> % V::size() == 0),
std::array<V, Vec_size_v<typename V::value_type, Type> / V::size()>
> split(const Vec<typename V::value_type, Type>&) noexcept {
return std::array<V, Vec_size_v<typename V::value_type, Type> / V::size()>();
}
int main() {
Vec<int, Const<6>> a;
split<Vec<int, Const<2>>, Const<6>>(a);
return 0;
}
Here (I think) the second split() can be matched, but I still got a compile error for the substitution fail of the first template. What is the reason for this? I have not yet found an entry in the C++ standard that can explain this problem. (This appears to be related to variadic templates, because if I modify the order of the template parameters and put the parameter pack in the first place, the code would compile correctly.)
SFINAE applies only if the invalid type or expression resulting from a use of a template parameter appears within:
a template parameter declaration within the same template parameter list
a default template argument which is used (or will be if overload resolution or class partial specialization matching selects the template)
for a function template, the declared type of the function (including its function parameter types, return type, or exception specification, but not when deducing a placeholder return type from return statements),
for a function template, its explicit(constant-expression) specifier, if any
for a class template partial specialization, the template arguments specified after the template name
See [temp.deduct]/8 - this is the "immediate context" rule.
Substitution of all type aliases and type alias templates happens essentially "before" template argument substitution, since [temp.alias]/2 says a use of the alias template is always equivalent to its substitution. For example, this explains why SFINAE applies to the ::type member lookup in a std::enable_if_t within a function type - it is equivalent to the written-out typedef std::enable_if<...>::type, so when this forms an invalid type, it's considered to be in the "immediate context" of the function template argument substitution. Type aliases don't actually get "instantiated" at all like function templates, class templates, and variable templates do.
When overload resolution considers the first split template, it tries to get the value of Vec_size_v<Vec<int, Const<2>>, Const<6>>, which causes an implicit instantiation of that specialization of the variable template. The evaluation of that variable template's initializer is within that variable template instantiation, not within the function template's function type, so SFINAE does not apply and the variable template has an error, even though it happened during a template argument deduction for overload resolution.
The obvious workaround, though probably not what you want, is to require the longer Vec_size<T, Type>::value instead of Vec_size_v<T, Type>.
Or you could give the primary template for vec_size_impl a static value member. But it doesn't actually need to have a numeric type: if you do
template <class T, class Type, class = void>
struct vec_size_impl
{
struct none_t {};
static constexpr none_t value;
};
// partial specialization as before
template <class T, class Type>
inline constexpr auto Vec_size = vec_size_impl<T, Type>::value;
then the same declaration of the first split would get an actual valid constant expression for its Vec_size_v use, but an invalid expression (0 + ... + Sizes) == Vec_size_v<T, Type> since there's no matching operator==. But this invalid expression is within the function template's function type, so then SFINAE can discard the function template from the overload resolution process.
Thanks and the question is solved now. The error message is as follows:
cxx.cpp:24:62: error: no member named 'value' in 'vec_size_impl<Vec<int, Const<2>>, Const<6>>'
inline constexpr size_t Vec_size_v = vec_size_impl<T, Type>::value;
~~~~~~~~~~~~~~~~~~~~~~~~^
cxx.cpp:29:27: note: in instantiation of variable template specialization 'Vec_size_v<Vec<int, Const<2>>, Const<6>>' requested here
((0 + ... + Sizes) == Vec_size_v<T, Type>),
^
cxx.cpp:46:3: note: while substituting explicitly-specified template arguments into function template 'split'
split<Vec<int, Const<2>>, Const<6>>(a);
^
and the error is because the substitution failure is a hard error, since the deduction of Vec_size_impl::value is not in the immediate context. The question can be solved by replacing the Vec_size_v<T, Type> by Vec_size_impl<T, Type>::value in the enable_if.
I'm getting the error:
Error C2154 '_Ty': only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type'
I thought it shouldn't be resolving underlying_type to underlying_type, because I first check whether T is an enum. Here is the code:
template <typename T>
struct Foo
{
static inline constexpr bool isArgIntegral = std::is_integral<T>::value;
static inline constexpr bool isArgEnum = std::is_enum_v<T>;
using integral_underlying_type = std::conditional<isArgEnum, std::underlying_type_t<T>, T>;
};
int main()
Foo<int> g; // only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type'
}
So is it the case that in a call to std::conditional, instead of first checking the condition (1st argument), it creates the classes of the 2nd and 3rd arguments regardless of the condition, and hence why I'm getting the error that I can't call underlying_type with an 'int'?
How do I go about getting the integral type of the T template argument, whether it's an integral or an enum?
Edit: My next attempt is to place the typedef in an if constexpr:
if constexpr (std::is_enum_v<T>)
{
using integral_underlying_type = std::underlying_type_t<T>;
// Now std::underlying_type_t won't be called at all unless T is enum, right?
}
Unfortunately, std::integral_underlying_type is not SFINAE friendly until C++20.
You can then delay instantiation of std::underlying_type<T>::type:
template <typename T>
struct Foo
{
using integral_underlying_type =
typename std::conditional_t<std::is_enum_v<T>,
std::underlying_type<T>,
type_identity<T>>::type;
};
Notice the double ::type in std::conditional<cond, T1, T2>::type::type (hidden with _t). The extra ::type is done outside the conditional instead of inside (and so the need of type_identity).
Yes. The problem is that whether the condition isArgEnum is true or false (i.e. whether T is enum or not), std::underlying_type_t<T> has to be specified as the template argument for std::conditional.
You can apply partial specialization like
template <typename T, typename = void>
struct Foo
{};
template <typename T>
struct Foo<T, std::enable_if_t<std::is_enum_v<T>>>
{
using integral_underlying_type = std::underlying_type_t<T>;
};
template <typename T>
struct Foo<T, std::enable_if_t<std::is_integral_v<T>>>
{
using integral_underlying_type = T;
};
The title is a bit confusing but what I mean is this specific case:
template<class>
struct get_type_of_nontype;
template<class T, T Value, template<T> class Template>
struct get_type_of_nontype<Template<Value>> {
using type = T;
};
So I can use it like this:
#include <type_traits>
template<int I>
class int_non_type {};
static_assert(
std::is_same<typename get_type_of_nontype<int_non_type<0>>::type, int>::value,
"T is deduced to be `int` as `template<T> class Template` is `template<int> class int_non_type`"
);
This works fine in C++17. In C++14 I get the following errors:
gcc 8:
<source>:5:8: error: template parameters not deducible in partial specialization:
struct get_type_of_nontype<Template<Value>> {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:5:8: note: 'T'
clang 7:
<source>:5:8: error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct get_type_of_nontype<Template<Value>> {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:4:16: note: non-deducible template parameter 'T'
template<class T, T Value, template<T> class Template>
^
And then they both complain that struct get_type_of_nontype<int_non_type<0>> is incomplete, so typename get_type_of_non_type<int_non_type<0>>::type can't compile.
Why is this different between C++14 and C++17? Is this just a compiler bug? If not, is there a way to do this in C++14?
The Standard wording in [temp.deduct.type] paragraphs 13 and 14 changed. So yes, your example is invalid in C++14, but is allowed in C++17 thanks to a new language feature.
C++14:
A template type argument cannot be deduced from the type of a non-type template-argument.
[Example:
template<class T, T i> void f(double a[10][i]);
int v[10][20];
f(v); // error: argument for template-parameter T cannot be deduced
-- end example]
C++17:
When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value. [Example:
template<long n> struct A { };
template<typename T> struct C;
template<typename T, T n> struct C<A<n>> {
using Q = T;
};
using R = long;
using R = C<A<2>>::Q; // OK; T was deduced to long from the
// template argument value in the type A<2>
-- end example] The type of N in the type T[N] is std::size_t. [Example:
template<typename T> struct S;
template<typename T, T n> struct S<int[n]> {
using Q = T;
};
using V = decltype(sizeof 0);
using V = S<int[42]>::Q; // OK; T was deduced to std::size_t from the type int[42]
-- end example]
[Example:
template<class T, T i> void f(int (&a)[i]);
int v[10];
void g() {
f(v); // OK: T is std::size_t
}
-- end example]
This seems a bit related to another C++17 template change: C++17 is the first version to allow placeholder types in non-type template parameters, as in template <auto Value> or template <auto* Ptr>. I'd expect compiler implementations would need some similar logic for supporting both of the two language features.
I created two simple functions which get template parameters and an empty struct defining a type:
//S<T>::type results in T&
template <class T>
struct S
{
typedef typename T& type;
};
//Example 1: get one parameter by reference and return it by value
template <class A>
A
temp(typename S<A>::type a1)
{
return a1;
}
//Example 2: get two parameters by reference, perform the sum and return it
template <class A, class B>
B
temp2(typename S<A>::type a1, B a2)//typename struct S<B>::type a2)
{
return a1 + a2;
}
The argument type is applied to the struct S to get the reference. I call them with some integer values but the compiler is unable to deduce the arguments:
int main()
{
char c=6;
int d=7;
int res = temp(c);
int res2 = temp2(d,7);
}
Error 1 error C2783: 'A
temp(S::type)' : could not deduce
template argument for 'A'
Error 2 error C2783: 'B
temp2(S::type,B)' : could not
deduce template argument for 'A'
Why is this happening? Is it that hard to see that the template arguments are char and int values?
Just as first note, typename name is used when you mention a dependent name. So you don't need it here.
template <class T>
struct S
{
typedef T& type;
};
Regarding the template instantiation, the problem is that typename S<A>::type characterizes a nondeduced context for A. When a template parameter is used only in a nondeduced context (the case for A in your functions) it's not taken into consideration for template argument deduction. The details are at section 14.8.2.4 of the C++ Standard (2003).
To make your call work, you need to explicitly specify the type:
temp<char>(c);
It is looks like nondeduced context. According to C++ Standard 14.8.2.4/4:
The nondeduced contexts are:
The nested-name-specifier of a type that was specified using a qualified-id.
A type that is a template-id in which one or more of the template-arguments is an expression that references a template-parameter.
When a type name is specified in a way that includes a nondeduced context, all of the types that comprise that type name are also nondeduced. However, a compound type can include both deduced and nondeduced types. [Example: If a type is specified as A<T>::B<T2>, both T and T2 are nondeduced. Likewise, if a type is specified as A<I+J>::X<T>, I, J, and T are nondeduced. If a type is specified as void f(typename A<T>::B, A<T>), the T in A<T>::B is nondeduced but the T in A<T> is deduced. ]
Deduction works in the forward direction:
template <class T> void f(T);
f(2); // can deduce int from T
Why is this happening?
It doesn't work in the backwards direction (your example):
template <class A> void g(typename S<A>::type);
Is it that hard to see that the template arguments are char and int values?
Template deduction can do some magical (Turing-complete) things, but I don't think this is one of them.
You might use something like (untested):
template <class SA> void h(SA a1)
{
STATIC_ASSERT(same_type<SA, S<A>::type>::value);
typedef typename SA::type A;
...
}
Using your favorite static assert library (Boost has two).
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.