In my example I have a class Foo<T>. In my function test I need to get the template parameter of Foo otherwise the normal type. First I started to use std::conditional but forgot that the template parameters must all be valid, no matter which one is picked. Is the only way to create a type-specialisation for non-Foo types?
Example
#include <type_traits>
template <typename TYPE>
class Foo
{
public:
using M = TYPE;
};
template <typename T>
void test(const T& a)
{
// actually I would have used !is_foo<T>::value for the first arg
// but this check is fine to minimise the example
using MY_TYPE = typename std::conditional<
std::is_same<T, int>::value,
T,
typename T::M>::type; // <---Error: error: type 'int' cannot be used prior to '::' because it has no members
}
int main()
{
test(Foo<int>()); // MY_TYPE must be int
test(int()); // MY_TYPE must be int
return 0;
}
Well you could make an UnFoo helper to get the right type for you:
template <typename T>
struct UnFoo {
using type = T;
};
template <typename T>
struct UnFoo<Foo<T>> {
using type = T;
};
template <typename T>
void test(const T& a)
{
using MY_TYPE = typename UnFoo<T>::type; //maybe with a helper to get rid of typename
}
Another option would be to write an overload for Foo<T> and have it delegate to the other function, but that depends on what your real test function does.
You can do some void_t magic to allow SFINAE to figure help you out:
#include <type_traits>
#include <iostream>
#include <typeinfo>
template <typename TYPE>
class Foo
{
public:
using M = TYPE;
};
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
// primary template handles types that have no nested ::T member:
template< class T, class = void_t<> >
struct M_or_T { using type = T; };
// specialization recognizes types that do have a nested ::T member:
template< class T >
struct M_or_T<T, void_t<typename T::M>> { using type = typename T::M; };
template <typename T>
void test(const T& a)
{
using MY_TYPE = typename M_or_T<T>::type;
std::cout << typeid(MY_TYPE).name() << "\n";
}
int main()
{
test(Foo<int>()); // MY_TYPE must be int
test(int()); // MY_TYPE must be int
return 0;
}
What happens is that the second overload of M_or_T substitution fails for int (and for any type without a type member M) and thus the first overload is chosen. For types which have a type member M, a more specialized second overload is chosen.
#include <type_traits>
template <typename TYPE>
class Foo
{
public:
using M = TYPE;
};
template <typename T>
void test(const Foo<T>& a)
{
using MY_TYPE = Foo<T>::M;
testOther<MY_TYPE>(a);
}
template <typename T>
void test(const T& a)
{
using MY_TYPE = T;
testOther<MY_TYPE>(a);
}
template <typename T, typename S>
void testOther(const S& a)
{
// do stuff
}
int main()
{
test(Foo<int>()); // MY_TYPE must be int
test(int()); // MY_TYPE must be int
return 0;
}
I'm not exactly sure what you wanted, but I hope this is what you wanted. It might be a bit off. I didn't compile this.
Related
I am new to C++20. The intention here is to have a template class which has value whose type would be the underlying type of T that's passed in.
So in case of T being:
std::optional<char>, it's char value
int, it's just int value.
Is there any better way to extract the types than through struct TypeExtract? More or a generic solution in C++20 perhaps? Given if the class could take more than just std::optional<int> or just a primitive type?
Can the condition in foo be improved specially with the way val is initialized?
template<typename T>
constexpr bool is_optional = false;
template<typename T>
constexpr bool is_optional<std::optional<T>> = true;
template<typename T>
struct TypeExtract
{
using type = T;
};
template<typename T>
struct TypeExtract<std::optional<T>>
{
using type = T;
};
template <typename T>
concept is_integral = std::is_integral_v<typename TypeExtract<T>::type>;
template <is_integral T>
class A
{
using Type = typename TypeExtract<T>::type;
Type val;
void foo(T value)
{
if constexpr (is_optional<T>)
{
val = *value;
}
else
{
val = value;
}
}
};
int main()
{
A<char> a1;
A<std::optional<int>> a2;
// A<double> a3; // fails
}
It looks like you're trying to extract first template parameter from a class template and keep on unwinding templates until you get to a non-template type. In that case you could make a type trait that is specialized for types instantiated from templates:
// primary template
template<class T, class...>
struct type {
using value_type = T;
};
// specialization for template instantiated types
template<template<class, class...> class T, class F, class... Rest>
struct type<T<F, Rest...>> {
using value_type = typename type<F>::value_type;
};
// helper alias
template<class... Ts>
using type_t = typename type<Ts...>::value_type;
You could then use it like so:
int main() {
type_t<char> a1;
type_t<std::optional<int>> a2;
type_t<double, int> a3;
static_assert(std::is_same_v<decltype(a1), char>);
static_assert(std::is_same_v<decltype(a2), int>);
static_assert(std::is_same_v<decltype(a3), double>);
}
There is no good or bad here, it's a matter of style and convention, but personally I would get rid of if constexpr and take advantage of trailing requires for the sake of reducing function's cyclomatic complexity. On the other hand, that add some boilerplate. The choice is yours.
Not much can be done about type extraction, though I would probably use a templated base and import its member(s) instead of importing the type into the class. Not a big deal, but it feels more idiomatic to me.
As for concepts, I'd probably use more library-provided ones in the place of type traits.
Side note: consider using assert, .value() or similar function when assigning from the optional to ensure it's not empty.
All in all, I'd probably write your code somewhat this way:
#include <concepts>
#include <type_traits>
#include <optional>
template<typename T>
concept StdOptional = std::same_as<std::optional<typename T::value_type>, T>;
template<typename T>
concept OptionalIntegral = StdOptional<T> and std::integral<typename T::value_type>;
template<typename T>
concept OptionalOrOptionalIntegral = std::integral<T> or OptionalIntegral<T>;
template<typename>
struct ABase;
template<std::integral T>
struct ABase<T>
{
T value;
};
template<OptionalIntegral T>
struct ABase<T>
{
typename T::value_type value;
};
template<OptionalOrOptionalIntegral T>
class A : ABase<T>
{
using ABase<T>::value;
public:
void setValue(T val) requires(std::integral<T>)
{
value = val;
}
void setValue(T val) requires(OptionalIntegral<T>)
{
value = val.value();
}
};
Demo: https://godbolt.org/z/dzvr9xbGr
I have the following type, which I get with decltype
QString UserInfo::*&
I can remove the & part by wrapping decltype with std::remove_reference_t but I also want to remove UserInfo::* part
How can I do that so I can use only QString type in my templates
I'm using this template in initializer list where I don't have access to solid object or this pointer to .* operator in decltype
Using a valid object is not necessary in unevaluated contexts (like decltype). To exaggerate a little, you could even dereference a null pointer in there, and nothing bad would happen, since the dereference is never actually evaluated.
To create an object of a type that is not valid, but can be used in unevaluated contexts, you can use std::declval.
template<class T>
using member_type = decltype(std::declval<UserInfo>().*std::declval<T>());
Use template specialization to extract member type:
template <typename MemberType>
struct member_type;
template <typename Class, typename Member>
struct member_type<Member Class::*>
{
using type = Member;
};
template<typename T>
using member_type_t = typename member_type<T>::type;
class A
{
public:
int b;
};
using t = member_type_t<decltype(&A::b)>;
You can write a trait to extract the type of the member from the member function pointer:
#include <type_traits>
template <typename T>
struct member_type;
template <typename C, typename T>
struct member_type<T C::*> {
using type = T;
};
template <typename T>
using member_type_t = typename member_type<T>::type;
struct foo {
int bar;
};
int main()
{
int foo::*ptr = &foo::bar;
using T = member_type_t<decltype(ptr)>;
static_assert( std::is_same_v<int,T>);
}
Here is my version using C++17 in such way that decltype is not needed when template is used (a bit more handy):
#include <iostream>
#include <string>
#include <type_traits>
template <auto Fp>
struct field_type;
template<typename R, typename T, R (T::*FP)>
struct field_type<FP>
{
using type = R;
};
template<typename R, typename T, typename...Args, R (T::*FP)(Args...)>
struct field_type<FP>
{
using type = R;
};
template<auto FP>
using field_type_t = typename field_type<FP>::type;
class Foo {
public:
int x = 0;
double y = 0;
std::string s;
const int cx = 0;
Foo() = default;
void bar() {
std::cout << "bar\n";
}
int par(int z) {
std::cout << "bar\n";
return z;
}
};
template<auto F, typename T>
constexpr bool test = std::is_same_v<field_type_t<F>, T>;
static_assert(test<&Foo::x, int>, "");
static_assert(test<&Foo::cx, const int>, "");
static_assert(test<&Foo::s, std::string>, "");
static_assert(test<&Foo::y, double>, "");
#ifndef HIDE_PROBLEM_ON_GCC_11
static_assert(test<&Foo::bar, void>, "");
static_assert(test<&Foo::par, int>, "");
#endif
For some strange reason works on all compilers, but not for gcc 11.1 or newer.
https://godbolt.org/z/4e7oKbod1
I get an error:
error: default template arguments may not be used in partial specializations
in the following code:
#include <iostream>
#include <type_traits>
#include <vector>
enum class MyEnum
{
aaa,
bbb,
};
template<class T>
struct is_vector_enum
{
using type = T ;
constexpr static bool value = false;
};
template<class T, class std::enable_if<std::is_enum<T>::value>::type* = nullptr> // Error ....
struct is_vector_enum<std::vector<T>>
{
using type = std::vector<T> ;
constexpr static bool value = true;
};
int main()
{
std::cout << "is_vector_enum: " << is_vector_enum<std::vector<MyEnum>>::value << std::endl;
}
The purpose is to detect whether a type is a vector of enum.
How should I fix this code?
Your primary template and your specialization need to have the same number of template parameters. At the moment, your primary has 1:
template<class T>
struct is_vector_enum
and your specialization has 2:
template<class T, class std::enable_if<std::is_enum<T>::value>::type* = nullptr>
struct is_vector_enum<std::vector<T>>
The typical way to do this in C++17 is to provide a dummy 2nd template parameter to the primary, that defaults to void, to then let you do the SFINAE in the second parameter:
template <class T, class Enable=void>
struct is_vector_enum { /* ... */ };
template <class T>
struct is_vector_enum<std::vector<T>, std::enable_if_t<std::is_enum_v<T>>> { /* ... */ };
A different way entirely to do this would be:
template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
std::true_type impl(std::vector<T> const&);
template <typename T>
std::false_type impl(T const&);
template <typename U>
using is_vector_enum = decltype(impl(std::declval<T>()));
Note that the impl functions here are not defined, and are not intended to be invoked.
Specializations are allowed to have a different number of template parameters than the primary. In fact, this happens quite often. However, as the error indicates, you are not allowed to give any of them default arguments.
That aside, I prefer simplicity, when possible.
template <typename T>
struct is_vector_enum : std::false_type { };
template <typename T>
struct is_vector_enum<std::vector<T>> : std::is_enum<T> { };
Why compiler can deduce T with this code:
#include <vector>
template<typename T>
void foo(T& t) {}
int main(void) {
std::vector<uint8_t> vec = { 1,2,3 };
foo(vec);
return 0;
}
But fails with this code:
#include <vector>
#include <type_traits>
template<typename T>
void foo(typename std::enable_if<true, T>::type& t) {}
int main(void) {
std::vector<uint8_t> vec = { 1,2,3 };
foo(vec);
return 0;
}
I want to use second construct, to select between two template functions, based on passed class method existence.
In the second case you have a non-deduced context, in other words, the compiler cannot deduce the type.
The simplest example of a non-deduced context is
template<typename T>
struct Id
{
using type = T;
};
template<typename T>
void foo(typename Id<T>::type arg); // T cannot be deduced
As explained by vsoftco, you have a non-deduced context.
For SFINAE, you may use one of the following:
template<typename T>
std::enable_if_t<condition_dependent_of_T, ReturnType>
foo(T& t) {}
or
template<typename T, std::enable_if_t<condition_dependent_of_T>* = nullptr>
ReturnType foo(T& t) {}
To visualize the problem let's analyse an example:
template <class>
struct foo {
using type = float;
};
template <>
struct foo<bool> {
using type = int;
};
template <>
struct foo<int> {
using type = int;
};
template <class T>
void bar(foo<T>::type t) { }
int main() {
bar(int{});
}
Now in the line bar(int{}); both types bool as well as int matches to a template parameter T. Which one value should be deduced then? This is only the one example why non-deduced context is strictly necessary!
Below SFINAE code with variadic templates compiles nicely using clang 3.7.1, C++14:
#include <array>
#include <iostream>
#include <vector>
#include <cstdint>
enum class Bar : uint8_t {
ay, bee, see
};
struct S {
static void foo() {}
// std::begin(h) is defined for h of type H
template<typename H, typename... T>
static typename std::enable_if<std::is_pointer<decltype(std::begin(std::declval<H>()))*>::value>::type
foo(const H&, T&&... t)
{ std::cout << "container\n"; foo(std::forward<T>(t)...); }
// H is integral
template<typename H, typename... T>
static typename std::enable_if<std::is_integral<typename std::remove_reference<H>::type>::value>::type
foo(const H&, T&&... t)
{ std::cout << "integer\n"; foo(std::forward<T>(t)...); }
// H is an enum with underlying type = uint8_t
/*
template<typename H, typename... T>
static typename std::enable_if<std::is_same<typename std::underlying_type<H>::type,uint8_t>::value>::type
foo(const H&, T&&... t)
{ std::cout << "enum\n"; foo(std::forward<T>(t)...); }
*/
};
int main()
{
S::foo(std::array<int,8>(), 5, 5L, std::vector<int>{}, 5L);
}
I want the correct overload of foo to be called recursively, based on the type H:
if std::begin(h) is defined for an h of type H, I want the
overload number 1 to be chosen
if H is an "integral type", I want overload number 2.
This works as it is. But if I add another overload for enum types (you can try to un-comment the third overload), then I get:
error: only enumeration types have underlying types
I agree that only enums got an underlying type, hence why is Not the third overload (with std::underlying_type) get SFINAE-d away?
std::underlying_type is not SFINAE friendly. Attempting to access std::underlying_type<T>::type for a non-enumeration type results in undefined behavior (often a hard error), not substitution failure.
You need to ascertain that the type at issue is an enumeration type first, before attempting to access its underlying type. Writing this in line would be something along the lines of typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type. Replacing the typename std::underlying_type<H>::type in your return type with this hideous mess and you get an even more hideous mess that works :)
If you find yourself needing to do this often - or just don't want to write typename std::enable_if<std::is_same<typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type, uint8_t>::value>::type - you can write a SFINAE-friendly underlying_type:
template<class T, bool = std::is_enum<T>::value>
struct safe_underlying_type : std::underlying_type<T> {};
template<class T>
struct safe_underlying_type<T, false /* is_enum */> {};
Here's a solution inspired from T.C.'s solution that worked for my use case:
template <typename T, bool = std::is_enum<T>::value>
struct relaxed_underlying_type {
using type = typename std::underlying_type<T>::type;
};
template <typename T>
struct relaxed_underlying_type<T, false> {
using type = T;
};
Example Usage:
template <typename T>
struct UnwrapEnum {
using type =
typename std::conditional<
std::is_enum<T>::value,
typename relaxed_underlying_type<T>::type,
T>
::type;
};
enum class MyEnum : int {};
class MyClass {};
int main() {
UnwrapEnum<MyEnum>::type x;
static_assert(std::is_same<decltype(x), int>::value);
UnwrapEnum<MyClass>::type y;
static_assert(std::is_same<decltype(y), MyClass>::value);
return 0;
}