I have begun a project that makes heavy use of c 20 concepts as a way of learning some of the new c 20 features. As part of it, I have a function template that takes a single argument and operates on it. I wish to have the flexibility to pass types to this function that specify another type that they operate on, but that defaults to something else if that specification doesn't exist.
For example, here is a type that specifies a type that it operates on:
struct has_typedef_t
{
typedef int my_type; //operates on this type
void use_data(const my_type& data) const
{
//do something else
}
};
and one that does not specify a type, but operates on a default type specified elsewhere:
typedef std::string default_type;
struct has_no_typedef_t
{
void use_data(const default_type& data) const
{
//do something
}
};
I have a simple concept that can tell me if any given type has this specification:
template <class T> concept has_type = requires(T t) {typename T::my_type;};
And the function might look something like the following:
template <class T> void my_function(const T& t)
{
//Here I want a default-constructed value of the default
//type if the argument doesn't have a typedef
typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
t.use_data(input_data);
}
The problem here is that the second template argument is invalid for anything that doesn't specify a type, e.g. has_no_typedef_t
. An example program:
int main(int argc, char** argv)
{
has_no_typedef_t s1;
has_typedef_t s2;
// my_function(s1); // doesn't compile:
//'has_no_typedef_t has no type named 'my_type'
my_function(s2); //compiles
return 0;
}
What I am looking for is a replacement for the following line:
typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
as this is causing the problem. I am aware that I can pull of some tricks using an overloaded function using the concept above and combining decltype and declval, but I am looking for something clean and have not been able to come up with anything. How can I achieve this behavior cleanly?
Below is the full code for the full picture (c 20):
#include <iostream>
#include <type_traits>
#include <concepts>
#include <string>
template <class T> concept has_type = requires(T t) {typename T::my_type;};
struct has_typedef_t
{
typedef int my_type;
void use_data(const my_type& data) const
{
//do something
}
};
typedef std::string default_type;
struct has_no_typedef_t
{
void use_data(const default_type& data) const
{
//do something else
}
};
template <class T> void my_function(const T& t)
{
//Here I want a default-constructed value of the default
//type if the argument doesn't have a typedef
typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
t.use_data(input_data);
}
int main(int argc, char** argv)
{
has_no_typedef_t s1;
has_typedef_t s2;
// my_function(s1); // doesn't compile:
//'has_no_typedef_t has no type named 'my_type'
my_function(s2); //compiles
return 0;
}
CodePudding user response:
This has had a solution since C 98: a traits class. Concepts just makes it a bit easier to implement:
template<typename T>
struct traits
{
using type = default_type;
};
template<has_type T>
struct traits<T>
{
using type = T::my_type;
};
Without concepts, you'd need to use SFINAE to turn on/off the specializations based on whether the type has the trait or not.
CodePudding user response:
You can use lambda combined with if constexpr
to determine the type of the return. The type_identity
here is to solve the problem that the type may not be default_initializable
.
typename decltype([]{
if constexpr (requires { typename T::my_type; })
return std::type_identity<typename T::my_type>{};
else
return std::type_identity<default_type>{};
}())::type input_data;
t.use_data(input_data);