auto template parameter, data member and constness - c++

Suppose I have a pointer to data member and I want to know if it's const or not. In other words:
struct S {
const int i; // this is const
int j;
};
In C++ I used to do something like this:
template<typename Class, typename Type, Type Class:: *>
struct is_const_data_member: std::false_type {};
template<typename Class, typename Type, const Type Class:: *Member>
struct is_const_data_member<Class, const Type, Member>: std::true_type {};
template<typename Class, typename Type, Type Class:: *Member>
void foo() {
const auto bar = is_const_data_member<Class, Type, Member>::value;
// ...
}
However, now there is the auto template parameter and template parameters list are much elegant:
template<auto Member>
void foo() {
// ...
}
In this case, the solo way I found to know if the data member point to something that is const is:
const auto bar = std::is_const_v<std::remove_reference_t<decltype(std::declval<Class>().*Member)>>;
However, it looks ugly to me and I feel like there must be a better way to do it.
Is there any other (shorter) solution for that?

You could change is_const_data_member to operate on a single type template parameter:
template<typename MemPtr>
struct is_const_data_member: std::false_type {};
template<typename Class, typename Type>
struct is_const_data_member<const Type Class::*>: std::true_type {};
Then, from template<typename Class, typename Type, Type Class:: *Member> void foo() you use it as
is_const_data_member<Type Class::*>::value
(Which, in my opinion, is slightly more intuitive.)
And from template<auto Member> void foo() you use it as
is_const_data_member<decltype(Member)>::value
You could also rewrite the trait to operate on an auto template parameter. But by using a type parameter you avoid unnecessary instantinations for different pointers of same type, which is supposedly a good thing.

How about something like this:
template <typename T>
struct is_const_data_member : std::false_type {};
template <typename C, typename T>
struct is_const_data_member<const T C::*> : std::true_type {};
template <auto T>
constexpr bool is_const_data_member_v = is_const_data_member<decltype(T)>::value;
And then, for example
struct Test
{
int a;
const int b;
};
bool x = is_const_data_member_v<&Test::a>;
bool y = is_const_data_member_v<&Test::b>;
working test here

Related

Avoiding template parameter substitution completely

I have a class that can accept arithmetic types and std::complex. A simplified code of the class is
#include <complex>
template<typename T> struct is_complex : std::false_type {};
template<typename T> struct is_complex<std::complex<T>> : std::true_type {};
template<class T>
struct Foo {
void foo(typename T::value_type t)
requires (is_complex<T>::value) {
}
};
Now, I would like to take the internal type of std::complex and use it as the type of the parameters in the foo function.For example, if T is std::complex<double>, then I want the parameter types to be double.
This function should only be available when T is indeed std::complex.
I thought I could use typename T::value_type as the parameter type, since std::complex has a typedef value_type. Plus, I thought using requires here would avoid T to be substitued in this function in case T wasn't std::complex. Silly me.
The issue is that whenever I create a Foo<FundamentalType> the code breaks, since fundamentals don't have ::value_type.
int main() {
Foo<int> obj; // Breaks the code.
//obj.foo(4); // Function shouldn't be considered in overload resolution ideally...
Foo<std::complex<int>> obj2; // Works
obj2.foo(4); // Works as expected
}
Ideally, I would like the substitution of T to be ignored for this function in case T is not std::complex. Is that possible? If not, how can I circumvent this?
You're on the right track with is_complex: you'd like the same here, but with a different body of the type. For example,
template<typename T> struct complex_value_type {};
template<typename T> struct complex_value_type<std::complex<T>> { using type = T; };
template<typename T>
using complex_value_type_t = typename complex_value_type<T>::type;
Then, at any point, you can call it as complex_value_type_t<T>:
template<class T>
struct Foo {
template<typename T_ = T>
void foo(complex_value_type_t<T_> t)
requires (is_complex<T_>::value) {
}
};
The requires is not absolutely necessary then; it's already covered by complex_value_type_t<T> being defined only for complex<T>.
You just need some type to put in there, until requires can disable the function.
I would do this:
struct nullptr_value_type {using value_type = std::nullptr_t;};
using elem_or_null_t = typename std::conditional_t<is_complex<T>::value, T, nullptr_value_type>::value_type;
void foo(elem_or_null_t t)
requires (is_complex<T>::value)
{}
Use a template class as a template parameter.
#include <complex>
template<template<class> class T> struct is_complex : std::false_type {};
template<> struct is_complex<std::complex> : std::true_type {};
template<template<class> class T>
struct Foo {
void foo(typename T<double>::value_type t)//could be typename<T<TT>> if you made foo a templated function
requires (is_complex<T>::value) {
}
};
int main(){
Foo<std::complex> f;
};
But you will need what to put into it when used. I just hard-coded double but you have to add a new template parameter to Foo or make foo a templated member function

How to test if a method is const?

How can I get a boolean value indicating if a known method has the const qualifier or not?
For example:
struct A {
void method() const {}
};
struct B {
void method() {}
};
bool testA = method_is_const<A::method>::value; // Should be true
bool testB = method_is_const<B::method>::value; // Should be false
In the type_traits header I found an is_const test I could use, but I need the method type, and I'm unsure how to obtain that.
I tried: std::is_const<decltype(&A::method)>::value but it doesn't work, and I can understand why (void (*ptr)() const) != const void (*ptr)()).
It is a lot simpler to check whether a member function can be called on a const-qualified lvalue.
template<class T>
using const_lvalue_callable_foo_t = decltype(std::declval<const T&>().foo());
template<class T>
using has_const_lvalue_callable_foo = std::experimental::is_detected<const_lvalue_callable_foo_t, T>;
Rinse and repeat, except with std::declval<const T>(), to check if said function can be called on a const-qualified rvalue. I can think of no good use cases for const && member functions, so whether there's a point in detecting this case is questionable.
Consult the current Library Fundamentals 2 TS working draft on how to implement is_detected.
It is a lot more convoluted to check whether a particular pointer-to-member-function type points to a function type with a particular cv-qualifier-seq. That requires 6 partial specializations per cv-qualifier-seq (const and const volatile are different cv-qualifier-seqs), and still can't handle overloaded member functions or member function templates. Sketching the idea:
template<class T>
struct is_pointer_to_const_member_function : std::false_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args...) const> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args...) const &> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args...) const &&> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args..., ...) const> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args..., ...) const &> : std::true_type {};
template<class R, class T, class... Args>
struct is_pointer_to_const_member_function<R (T::*)(Args..., ...) const &&> : std::true_type {};
If you want const volatile to be true too, stamp out another 6 partial specializations along these lines.
The reason std::is_const<decltype(&A::method)>::value doesn't work is that a const member function isn't a const (member function). It's not a top-level const in the way that it would be for const int vs int.
What we can do instead is a type trait using void_t that tests whether we can call method on a const T:
template <typename... >
using void_t = void;
template <typename T, typename = void>
struct is_const_callable_method : std::false_type { };
template <typename T>
struct is_const_callable_method<T, void_t<
decltype(std::declval<const T&>().method())
> > : std::true_type { };
Demo
In C++20, things get a lot easier because concepts have been standardized, which subsumes the detection idiom.
Now all we need to write is this constraint:
template<class T>
concept ConstCallableMethod = requires(const T& _instance) {
{ _instance.method() }
};
ConstCallableMethod tests that the expression _instance.has_method() is well formed given that _instance is a const-reference type.
Given your two classes:
struct A {
void method() const { }
};
struct B {
void method() { }
};
The constraint will be true for A (ConstCallableMethod<A>) and false for B.
If you wish to also test that the return type of the method function is void, you can add ->void to the constraint like so:
template<class T>
concept ConstCallableMethodReturnsVoid = requires(const T& _instance) {
{ _instance.method() } -> void
};
If you wish to be a little more generic, you can pass in a member function pointer to the concept and test if that function pointer can be called with a const instance (although this gets a little less useful when you have overloads):
template<class T, class MemberF>
concept ConstCallableMemberReturnsVoid = requires(const T& _instance, MemberF _member_function) {
{ (_instance.*_member_function)() } -> void
};
You'd call it like so:
ConstCallableMemberReturnsVoid<A, decltype(&A::method)>
This allows for some other theoretical class like C, that has a const method, but it's not named method:
struct C
{
void foobar() const{}
};
And we can use the same concept to test:
ConstCallableMemberReturnsVoid<C, decltype(&C::foobar)>
Live Demo
Create a type trait to determine the const-ness of a method:
template<typename method_t>
struct is_const_method;
template<typename CClass, typename ReturnType, typename ...ArgType>
struct is_const_method< ReturnType (CClass::*)(ArgType...)>{
static constexpr bool value = false;
};
template<typename CClass, typename ReturnType, typename ...ArgType>
struct is_const_method< ReturnType (CClass::*)(ArgType) const>{
static constexpr bool value = true;
};

has_type template returns true for struct type {};

There are a number of ways to implement a has_type<T> template that deduces if T has a nested class or typedef named type. ie
namespace detail {
template<typename> struct tovoid { typedef void type; };
}
template<typename T, typename = void> struct has_type
: std::false_type { };
// this one will only be selected if C::type is valid
template<typename C> struct has_type<C, typename detail::tovoid<typename C::type>::type>
: std::true_type { };
Or
template <typename C> char test_for_type(...) { return '0'; }
template <typename C> double test_for_type(typename C::type const *) { return 0.0; }
template <typename T> struct has_type
{
static const bool value = sizeof(test_for_type<T>(0)) == sizeof(double);
};
however in either case, has_type<type>::value is true for this class:
struct type
{
};
Now the above type doesn't have another type nested within it, but it does have a constructor type::type().
But should that constructor 'trigger' the checks for the nested type? Or is it a compiler bug?
(I would like to think that typename type::type didn't apply to a constructor and/or that you couldn't take a pointer to a constructor, such as what would be produced by the second test method: typename type::type const *.
?
The name of a class is "injected" into the scope of the class, so type::type really is the name of a type, and it's the same type as ::type.

Specializing function template for templated derived class

I essentially have a mock version of std::integral_constant that includes a variable and I want to specialize a function template for these classes derived from Base<T>, like this:
template<class T> struct Base{
typedef T type;
T t;
};
template<class T> struct A : Base<T>{
static constexpr T value = 1;
};
template<class T> struct B : Base<T>{
static constexpr T value = 2;
};
struct Unrelated{};
// etc.
template<class T> void foo(T t){
//I would like to specialize foo for A and B and have a version for other types
}
int main(){
foo(A<float>());//do something special based on value fields of A and B
foo(B<float>());
foo(Unrelated()); //do some default behavior
}
Here are the main issues:
I cannot include value as a template as I am expecting T = double, float, or some other non-integral types (otherwise I'd just extend std::integral_constant)
I can't cleanly use std::is_base as I would have to do std::is_base<Base<T::type>,T>
Doing foo(Base<T>&) wouldn't allow me to see value and I don't want to have to resort to a virtual value() function (or reflection).
And obviously I would like to avoid specializing foo for every derived class.
I think the answer lies in using is_base but I haven't been able to get it to work no matter how I tried to use it. Is there a much simpler way I am missing?
The following should work:
template<typename,typename = void>
struct IsBase
: std::false_type {};
template<typename T>
struct IsBase<T, typename std::enable_if<
std::is_base_of<Base<typename T::type>,T>::value
>::type>
: std::true_type {};
template<class T>
typename std::enable_if<IsBase<T>::value>::type foo(T t){
// use T::value
}
template<class T>
typename std::enable_if<!IsBase<T>::value>::type foo(T t){
// general case
}
Live example

Perform overload resolution with template meta-programming

Inspired by another question I tried to find a way to deduce the type
of an overload member function given the actual argument used to call
that function. Here is what I have so far:
#include <type_traits>
template<typename F, typename Arg>
struct mem_fun_type {
// perform overload resolution here
typedef decltype(std::declval<F>()(std::declval<Arg>())) result_type;
typedef decltype(static_cast<result_type (F::*)(Arg)>(&F::operator())) type;
};
struct foo {};
struct takes_two
{
void operator()(int);
void operator()(foo);
};
struct take_one {
void operator()(float);
};
int main()
{
static_assert(std::is_same<mem_fun_type<take_one, float>::type,
void (take_one::*)(float)>::value, "Zonk");
static_assert(std::is_same<mem_fun_type<takes_two, double>::type,
void (takes_two::*)(float)>::value, "Zonk");
return 0;
}
As long as the template parameter Arg matches the actual type the
static_cast will succeed, but this is only the most trivial case of
overload resolution (exact match). Is it possible to perform the
complete overload resolution process in template metaprogramming?
This is purely hypothetical and not intended for real-world use.
This is closest I came to it so far: define function returning tables of different sizes and your result is sizeof(select(...)) receiving pointer to function you want to match. To ensure that the code will compile even if function does not exist in given class, you can use separate check has_function.
Result of overload resolution is in select<has_function<T>::value, T>::value.
With this code you can even "resolve" data members, not just functions, it's only a question of making right parameter for select function.
However there is one deficiency here - overload resolution is not on function parameters, but on function type. Meaning none of usual parameter type conversions takes place.
// Verify the name is valid
template <typename T>
struct has_function
{
struct F {int function;};
struct D : T, F {};
template <typename U, U> struct same_;
template <typename C> static char(&select_(same_<int F::*, &C::function>*))[1];
template <typename> static char(&select_(...))[2];
enum {value = sizeof(select_<D>(0)) == 2};
};
// Values to report overload results
enum type { none=1 , function_sz_size_t , function_sz , function_string };
template <bool, typename R> struct select;
template <typename R> struct select<false, R>
{
enum {value = none};
};
template <typename R> struct select<true, R>
{
// Define your overloads here, they don't have to be templates.
template <typename Ret, typename Arg> static char(&select_(Ret (R::*)(const char*, Arg)))[function_sz_size_t];
template <typename Ret, typename Arg> static char(&select_(Ret (R::*)(Arg)))[function_sz];
template <typename Ret> static char(&select_(Ret (R::*)(std::string)))[function_string];
template <typename Ret> static char(&select_(Ret (R::*)(std::string&&)))[function_string];
template <typename Ret> static char(&select_(Ret (R::*)(const std::string&)))[function_string];
static char(&select_(...))[none];
enum {value = sizeof(select_(&R::function))};
};