How to make _t version of SFINAE struct exposing static member value? - c++

I have that code that recognizes which GL type you need to use based on C++ types. I want to make a _t version of it (like std::decay_t or std::enable_if_t) but expose int constant value
template <typename T, typename = void> struct GLType {};
template <typename T>
struct GLType<T, std::enable_if_t<std::is_same_v<std::remove_pointer_t<std::decay_t<T>>, float>>> {
const static constexpr int type = GL_FLOAT;
};
template <typename T>
struct GLType<T, std::enable_if_t<std::is_same_v<std::remove_pointer_t<std::decay_t<T>>, double>>> {
const static constexpr int type = GL_DOUBLE;
};
My first try was
template <typename T>
using GLType_t = GLType<T>::type;
but that doesn't work. Is it even possible to return value instead of type in the same way?
In the end, I want something like
int a = GLType_t<float>;
// instead of
int a = GLType<float>::type; // which works fine btw

You appear to be looking for variable-templates which allow you to do this:
template <typename T>
inline constexpr int GLType_t = GLType<T>::type;
and then you can use it like this:
int a = GLType_t<float>;
Also, I strongly suggest that you name the int member something like value instead of type. Names matter, and type is just the wrong name for a member that is not actually a type.

The #cigien's answer provided the way to go with variable template.
However, I would like to propose a less typing approach using c++17s if constexpr. The entire boiler-plate traits code will meltdown to simply, a template-function:
#include <type_traits>
template <typename T>
constexpr auto GLTypeHelper() noexcept
{
// assert is the T is not either float or double
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>, " T should be float or double");
if constexpr (std::is_same_v<T, float>)
return GL_FLOAT;
else if constexpr (std::is_same_v<T, double>)
return GL_DOUBLE;
};
// variable template for GLType_v
template <typename T>
inline constexpr int GLType_v = GLTypeHelper<T>(); // calls the `GLTypeHelper()`
You will use it like
constexpr int a = GLType_v<float>;
constexpr int b = GLType_v<double>;
and ofcourse, since GL_FLOAT and GL_DOUBLE are values, not a types, change the ::type -> ::value, and _t -> _v!

Related

How to limit parameter less template method to types of the own template class?

I have a template class that represents a special integer type.
A minimal implementation of this class could look like this:
template<typename T>
struct Int {
static_assert(std::is_integral_v<T>, "Requires integral type.");
using NT = T;
T v;
explicit constexpr Int(T v) noexcept : v{v} {}
template<typename U, std::enable_if_t<std::is_integral_v<U>, bool> = true>
constexpr auto cast() const noexcept -> Int<U> {
return Int<U>{static_cast<U>(v)};
}
template<typename U, typename U::NT = 0>
constexpr auto cast() const noexcept -> Int<typename U::NT> {
return Int<typename U::NT>{static_cast<typename U::NT>(v)};
}
};
There are a number of predefined type names for most common use-cases of the class:
using Int8 = Int<int8_t>;
using Int16 = Int<int16_t>;
using Int32 = Int<int32_t>;
using Int64 = Int<int64_t>;
The goal is to use the types of this class naturally, yet with a set of methods. One of these methods is a .cast<>() method to convert between the underlying integer types:
int main(int argc, const char *argv[]) {
auto a = Int32{10};
auto b = a.cast<int64_t>();
auto c = a.cast<Int64>();
}
To cover a wide range of uses, by the user and programatically in templates, the cast template argument shall allow the native type and also the template class as argument. Specifying int64_t, Int64 or therefore Int<int64_t> shall lead to the exact same result.
I would like to limit the second cast method to values of the Int template class.
The approach shown in the example, will work with any class that has a type definition called NT in its namespace. As in my library NT is commonly used in template classes, it does not limit the usage of the cast method enough for my liking.
The following example illustrates a case I would like to avoid:
struct Unrelated {
using NT = int32_t;
};
int main(int argc, const char *argv[]) {
auto a = Int32{10};
auto b = a.cast<Unrelated>(); // NO! is confusing, shouldn't work.
}
Is there a commonly used approach to "enable" a method only with template instances of the own class?
I am aware there are simple solutions in C++2x. Yet, I need a solution that is working with C++17.
The first cast method, accepting all integral types shall stay intact.
First a type trait from Igor Tandetnik (my own was uglier):
template<typename T> struct Int; // forward declaration
template <typename T> struct is_Int : std::false_type {};
template <typename T> struct is_Int<Int<T>> : std::true_type {};
template <typename T> inline constexpr bool is_Int_v = is_Int<T>::value;
Then you could define the class and its cast like so:
template<typename T>
struct Int {
static_assert(std::is_integral_v<T>); // no SFINAE needed so use static_assert
using NT = T;
T v;
explicit constexpr Int(T v) noexcept : v{v} {}
template<class U> // accept all, no SFINAE needed
constexpr auto cast() const noexcept {
// use static_assert instead
static_assert(std::is_integral_v<U> || is_Int_v<U>); // using the trait
// ...and constexpr-if:
if constexpr (std::is_integral_v<U>) return Int<U>{static_cast<U>(v)};
else return U{static_cast<typename U::NT>(v)};
}
};

C++17 how to test class has a member variable? [duplicate]

This question already has answers here:
How to detect whether there is a specific member variable in class?
(10 answers)
Closed 2 years ago.
I am going down the route
struct S {
static constexpr int extra=5;
};
struct V {
};
template <typename T>
void f()
{
if (std::is_same_v<decltype(T::extra), int>)
std::cout<< "extra exists" <<std::endl;
}
but calling f<S>() fails as
std::is_same_v<decltype(S::extra), int> == 0
and f<V>() does not compile
If you are stuck in c++17, there is some infrastructure that you can add to make detection like this much easier.
Detection-idiom
The most reusable/consistent way to detect features like this is via the Detection idiom, which leverages SFINAE through std::void_t in a template.
This can be taken verbatim from std::experimental::is_detected's page from cppreference. This effectively offers C++17 the ability to detect features in a way that is similiar to C++20's concepts; and the infrastructure can be reused easily for just about any detection.
The basics of what you would need are:
#include <type_traits>
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail
struct nonesuch{};
template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
Note: The above infrastructure can be reused for any detection. It is a very useful reusable tool in C++17.
With is_detected, all you need is a detector, which is simply a template alias that evaluates to a decltype expression of something that may, or may not, exist.
So in your case, to conditionally detect the presence of T::extra, you can do this with a simple detector like:
template <typename T>
using detect_extra = decltype(T::extra);
Putting it all together now, you can use this detector to conditionally toggle the branch:
if constexpr (is_detected<detect_extra,T>::value) {
// Only do code if 'T' has 'T::extra' (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra' (e.g. 'V')
}
Live Example
If equivalent conversion to a specific type is important, such as extra needing to be convertible to int, you can also use is_detected_convertible and use the detector to check for if the result can be convertible to the desired type. Using the same cppreference page again, you can define is_detected_convertible as:
template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
template <class To, template<class...> class Op, class... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
Which allows the check to instead be:
if constexpr (is_detected_convertible<int, detect_extra, T>::value) {
// Only do code if 'T' has 'T::extra' convertible to int (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra', or is not int
}
Live Example
Concepts (C++20+ only)
If you have access to c++20 and beyond, concepts make this much simpler -- since you can simply use a concept + requires clause like:
#include <concepts> // std::same_as
template <typename T>
concept HasExtra = requires(T) {
{T::extra} -> std::same_as<int>;
};
if constexpr (HasExtra<T>) {
// Only do code if 'T' has 'T::extra' and is 'int' (e.g. 'S')
} else {
// Only do code if 'T' does not have 'T::extra' (e.g. 'V')
}
Live Example
Observe that decltype(T::extra) (when T is S) is int const (is constexpr so is also const), not int. This explain why f<S>() fails.
To test if a class has a member variable there are many ways, I suppose.
A possible solution is develop something as
void type_extra (...);
template <typename T>
auto type_extra (T t) -> decltype(t.extra);
template <typename T>
using type_extra_t = decltype(type_extra(std::declval<T>()));
Now you can write f() as follows
template <typename T>
void f()
{
if ( not std::is_same_v<type_extra_t<T>, void> )
{
std::cout<< "extra exists" <<std::endl;
if ( std::is_same_v<type_extra_t<T>, int> )
std::cout<< "(and is int)" << std::endl;
}
}
Observe that now the type_extra_t<S> is int, not int const; this way (getting the type from the returned type of a function) loose the constness of the variable.
If you want maintain the constness, you can return a reference from the function (so it return a int const & in the S case)
template <typename T>
auto type_extra (T t) -> decltype(t.extra) &;
and remove the reference from the using (so, in the S case, int const & become int const)
template <typename T>
using type_extra_t
= std::remove_reference_t<decltype(type_extra(std::declval<T>()))>;

Is there a way to use variable template as a parameter?

I want to declare a function, which will take as a parameter a variable (let's say, int), which should be parametrized by a class. Speaking in terms of lambda calculus, I want my parameter to have a kind * -> int.
Example of a function I want to be able to write (Spec is the variable):
template <??? Specification, typename T>
auto make_array() {
return std::array<T, Specification<T>>;
}
Since C++14 we have variable templates, so we can do something like this:
template <typename T>
constexpr int digits = std::numeric_limits<T>::digits;
The problem is, how do I pass that into a function? In the notes section of cppreference it is stated that
Variable templates cannot be used as template template arguments.
But does that mean that there is actually no way to pass parametrized variable as a function parameter? What you can do is, for example, create a class which has a static field denoting value, but an obvious drawback is that the users of my function must derive from that class.
I believe there might be some workaround using SFINAE, but I lack skills in that area.
Unless you insist on using a variable template, you can use a type trait:
template <typename T> struct Specification;
you can specialize it for example for int:
template <>
struct Specification<int> {
static constexpr size_t value = 42;
};
and as you want to have different Specifications, pass it as template template parameter:
template <template<class> class S, typename T>
auto make_array() {
return std::array<T, S<T>::value>{};
}
Complete example:
#include <array>
#include <cstddef>
template <template<class> class S, typename T>
auto make_array() {
return std::array<T, S<T>::value>{};
}
template <typename T> struct Specification;
template <>
struct Specification<int> {
static constexpr size_t value = 42;
};
int main(){
auto x = make_array<Specification,int>();
}
Note that I was rather verbose for the sake of clarity. You can save a bit of typing by using std::integral_constant, eg:
template <>
struct Specification<double> : std::integral_constant<size_t,3> {};
As an follow-up on idclev's answer, you can avoid the need to explicitly specialise for the different types for individual "specifications" just by inheriting from e.g. integral_constant. For example, your desired usage was something like
template <template <typename T> int Specification, typename T>
auto make_array() {
return std::array<T, Specification<T>>;
}
template <typename T>
constexpr int digits = std::numeric_limits<T>::digits;
// ...
auto foo = make_array<digits, double>();
However, as you have noted, this is impossible: you cannot pass variable templates as template-template parameters. However, by turning digits into a structure directly you can do this:
template <template <typename T> class Specification, typename T>
auto make_array() {
// we no longer have the guarantee of the type here, unfortunately
// but you can use a static_assert to improve error messages
// (also using `std::size_t` here for correctness)
static_assert(
std::is_convertible<decltype(Specification<T>::value), std::size_t>::value,
"value must be convertible to size_t");
return std::array<T, Specification<T>::value>;
}
// use a type rather than a variable template
// we just inherit from integral constant to save on some typing
// (though you could do this explicitly as well)
template <typename T>
struct digits : std::integral_constant<int, std::numeric_limits<T>::digits> {};
// ...
// same call!
auto foo = make_array<digits, double>();

Converting std::type_identity object to a type

Suppose that we create two type_of functions that return std::type_identity, like:
template<auto VAR>
auto type_of() {
return std::type_identity<decltype(VAR)>{};
}
template<typename T>
auto type_of() {
return std::type_identity<T>{};
}
The way to get an actual type from std::type_identity seems a bit cumbersome:
// this works
// both i1 and i2 are ints
decltype(type_of<int>())::type i1;
decltype(type_of<int{}>())::type i2;
Is there a way to waive the need for decltype in above expressions, or to put it inside a reusable expression, to achieve something nicer like:
// can this work?
type_of<int>()::type i1;
type_of<int{}>()::type i2;
Or even better:
// can this work??
type_of_t<int> i1;
type_of_t<int{}> i2;
Note: specialization for type and non-type template parameter, which could have been a direction, doesn't work (cannot compile):
template<auto>
struct type_of;
template<typename T>
struct type_of<T> { // <== compilation error: type/value mismatch
using type = T;
};
template<auto VAR>
struct type_of<VAR> {
using type = decltype(VAR);
};
You can create a type alias. However you can't "overload" it. So my solution is to create two:
template <auto Var>
using type_of_var_t = decltype(type_of<Var>())::type;
template <class T>
using type_of_t = decltype(type_of<T>())::type;
auto test()
{
type_of_var_t<11> i1 = 24;
type_of_t<int> i2 = 17;
}
In C++, a template parameter must be a value, a type, or another template (which itself must fit within the declared template header). It must be exactly one of these.
If you want to do this:
the idea is to get something that is unaware on the caller side whether the template parameter is a type or a variable
The basic requirement for being able to do this is to write a template with a parameter that could be a value or a type.
That's not a thing C++ allows.
Template function overloading allows you to get away with something like that. But that only works because it isn't one template. It's two templates that are overloaded. Which one gets selected depends on the template arguments provided.
Template classes can't be overloaded. And template specialization cannot change the nature of the original template (like what its template parameters are). It can only allow you to reinterpret the template parameters of the original template parameters in order to provide an alternative implementation.
If you want this, you're going to have to wait until either C++ gets the ability to have a template parameter that could be anything or until C++ gets the ability to convert types into values and back (ie: reflection).
Getting the type from an std::type_identity object can be encapsulated into the following expresion:
template<auto x>
using type = typename decltype(x)::type;
This would allow to replace the cumbersome expressions:
decltype(type_of<int>())::type i1;
decltype(type_of<int{}>())::type i2;
With a more simple expression:
type<type_of<int>()> i1;
type<type_of<int{}>()> i2;
It still requires to go through two steps (type_of then type) as the first one shall be able to get a type or a variable, which is applicable only with function template overloading, then the function cannot return a type, so it returns an object that needs a template expression to extract the inner type.
Depending on what you want to do with the type, the code can become even simpler.
If all you want is to create an object of that type, you can forward the creation of the object into the function:
template<auto VAR, typename... Args>
auto create_type_of(Args&&... args) {
return decltype(VAR){std::forward<Args>(args)...};
}
template<typename T, typename... Args>
auto create_type_of(Args&&... args) {
return T{std::forward<Args>(args)...};
}
auto i1 = create_type_of<int>(7);
auto i2 = create_type_of<int{}>(7);
The generic case of creating a type from std::type_identity can work this way:
template<auto VAR>
constexpr auto type_of() {
return std::type_identity<decltype(VAR)>{};
}
template<typename T>
constexpr auto type_of() {
return std::type_identity<T>{};
}
template<typename T, typename... Args>
auto create(std::type_identity<T>, Args&&... args) {
return T{std::forward<Args>(args)...};
}
auto i1 = create(type_of<int>(), 7);
auto i2 = create(type_of<int{}>(), 7);
Note that the entire thing works only with variables that can be used as non-type template parameters. For a more generic approach that works without templates (but with a macro...), and thus can work for variables that cannot be template parameters, see this mind blowing neat answer!
By design, template arguments in C++ templates are either templates, types or values.
Within the template, you know which they are. All expressions within the template that are dependent on the template arguments are disambiguated using typename or template keywords (or context) so their category is known before arguments are substituted.
Now there are metaprogramming libraries that work with value-substitutes for all 3.
template<class T> struct type_tag_t {using type=T;};
template<class T> constexpr type_tag_t<T> tag={};
template<template<class...>class Z> struct template_z_t {
template<class...Ts>
using result = Z<Ts...>;
template<class...Ts>
constexpr result<Ts...> operator()( type_tag_t<Ts>... ) const { return {}; }
};
template<template<class...>class Z>
constexpr template_z_t<Z> template_z = {};
here templates are mapped to constexpr function objects. The template_z template lets you map type-templates over to this domain.
template<auto x>
using type = typename decltype(x)::type;
template<auto x>
constexpr std::integral_constant<std::decay_t<decltype(x)>, x> k = {};
So,
constexpr auto vector_z = template_z<std::vector>;
constexpr auto vector_tag = vector_z( tag<int>, tag<std::allocator<int>> );
then you can go back to the type:
type<vector_tag> v{1,2,3,4};
this probably isn't what you want.
You might be willing to check the proposal for Universal Template Parameters
The implication example quite match your use-case of specializing for both TTP and NTTP :
template <template auto>
struct X;
template <typename T>
struct X<T> {
// T is a type
using type = T;
};
template <auto val>
struct X<val> : std::integral_constant<decltype(val), val> {
// val is an NTTP
};

Is it possible to have a "generic" template parameter in C++, that can be either a non-type template parameter or a type?

In C++, it's possible to use a type as a template parameter, e.g:
template <typename T>
void MyFn();
It's also possible to use a non-type as a template parameter in some cases, e.g.:
template <int64_t T>
void MyFn2();
My question is whether it's possible to have a "generic" template parameter that can be both? Like:
template <TypenameOrint64_t T>
void MyFn3();
such that both MyFn3<42> and MyFn3<double> would be acceptable.
An example of how I might use this:
template <typename ValType, ValType Head, ValType ...Tail>
struct ListS{
template <typename OutType, template <ValType ArgType> class Fn>
using MapHead = ListS<OutType, Fn<Head>::val, Tail...>;
};
template<int64_t N>
struct SquareS{
static constexpr const int64_t val = N * N;
};
using Sqrd = ListS<int64_t, 3, 4>::MapHead<int64_t, SquareS>;
static_assert(std::is_same<Sqrd, ListS<int64_t, 9, 4>>::value, "Values don't match");
The above is a very rough sketch of a compile-time list of values along with a single compile-time "function" on it. Would it be possible to make something like that also support lists of types, not just lists of non-type template param compatible values, without just duplicating all the code?
Is it possible to have a “generic” template parameter in C++, that can be either a non-type template parameter or a type?
Short answer: no.
Long answer.
No. The best I can imagine to mix types and values is wrap values in types, using std::integral_constant, by example.
So, your desired code, could be written (C++17) almost as follows
#include <utility>
template <typename ...>
struct ListS;
template <typename ValType, ValType Head, ValType ...Tail>
struct ListS<std::integral_constant<ValType, Head>,
std::integral_constant<ValType, Tail>...>
{
template <template <auto> class Fn, typename OutType = ValType>
using MapHead = ListS<std::integral_constant<OutType, Fn<Head>::value>,
std::integral_constant<OutType, Tail>...>;
};
template <auto N>
struct SquareS : public std::integral_constant<decltype(N), N*N>
{ };
int main ()
{
using T1 = ListS<std::integral_constant<long long, 3ll>,
std::integral_constant<long long, 4ll>>;
using T2 = T1::MapHead<SquareS>;
using T3 = ListS<std::integral_constant<long long, 9ll>,
std::integral_constant<long long, 4ll>>;
static_assert( std::is_same_v<T2, T3> );
}
Pre C++17 you can't use auto for the type of the template values so you should make some simple corrections.
You could use function overloading and the auto type deduction that came with C++17
to accomplish something similar.
template<typename myType>
auto myFn3(myType value){
return value;
}
template<auto value> //takes any non-type parameter
auto myFn3(){
return value;
}
int main(){
auto test1_normal = myFn3(3);
auto test1_cast = myFn3<double>(3); //able to perform a cast
auto test1_auto = myFn3<3>();
return 0;
}