I am trying to modify the is_detected idiom to allow passing variadic arguments to it. I need this since some of my detected member functions will have user provided arguments.
So far, this is what I got working. You give the extra args to is_detected_args_v, and in theory, the template specialization would kick in and compile correctly. Thus giving std::true_type.
#include <type_traits>
#include <cstdio>
// slightly modified (and simplified) is_detected
template <template <class, class...> class Op, class T, class = void, class...>
struct is_detected_args : std::false_type {};
template <template <class, class...> class Op, class T, class... Args>
struct is_detected_args<Op, T, std::void_t<Op<T, Args...>>, Args...>
: std::true_type {};
template <template <class, class...> class Op, class T, class... Args>
inline constexpr bool is_detected_args_v
= is_detected_args<Op, T, Args...>::value;
// has_func, checks the function starts with int, and then Args&...
template <class T, class... Args>
using has_func = decltype(std::declval<T>().func(
std::declval<int>(), std::declval<Args&>()...));
// has the func
struct obj {
void func(int, double&, double&) {
printf("potato\n");
}
};
int main(int, char**) {
obj o;
if constexpr(is_detected_args_v<has_func, obj, double, double>) {
double d = 0;
double d2 = 42;
o.func(42, d, d2);
}
}
You can run the example here (tested on all 3 compilers) : https://wandbox.org/permlink/ttCmWSVl1XVZjty7
The problem is, the specialization is never chosen and the conditional is always false. My question is two-folds.
Is this even possible?
Why doesn't is_detected get specialized?
Thx
The main issue here is misunderstanding what void_t does. As a refresher, see how does void_t work?. The key idea is that the primary template has a void parameter and the specialization has some complex thing that you want to check wrapped in void_t so that it matches the primary template's parameter. That isn't happening in your example.
We can fix it in two easy steps. First, you have this type T along with Args... There isn't actually any reason to split this up, and it's easier to look at if we don't have extraneous parameters. So here's your attempt just reduced (I also gave a name to the parameter which is supposed to be void):
template <template <class...> class Op, class AlwaysVoid, class...>
struct is_detected_args : std::false_type {};
template <template <class...> class Op, class... Args>
struct is_detected_args<Op, std::void_t<Op<Args...>>, Args...>
: std::true_type {};
template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, Args...>::value;
Now it should be easier to see what's missing: the void parameter! You're not passing in a void and you need to. That's an easy fix though:
template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, void, Args...>::value;
// ~~~~~
And now it works as expected.
Cppreference also provides a complete implementation of is_detected if you want to look at that too.
Related
I have a template that takes a template template parameter and a pack of type arguments. I want to instantiate the template with the arguments only if the arities match. Something like this:
// can_apply_t = ???
template<template<typename...> typename Template, typename... Args>
struct test {
using type = std::conditional_t<can_apply_t<Template, Args...>, Template<Args...>, void>;
};
template<typename> struct unary_template;
template<typename, typename> struct binary_template;
static_assert(std::is_same_v< test<unary_template, int>::type, unary_template<int> >);
static_assert(std::is_same_v< test<binary_template, int>::type, void >);
I fantasized that it would be as simple as this:
template<template<typename... T> typename Template, typename... Args>
struct test {
using type = std::conditional_t<sizeof...(T) == sizeof...(Args), Template<Args...>, void>;
};
...but clang++12 says:
error: 'T' does not refer to the name of a parameter pack
fantasized that it would be as simple as this:
No... not as simple... when you write
using type = std::conditional_t<can_apply_t<Template, Args...>,
Template<Args...>,
void>;
the Template<Args...> must be an acceptable type also when test of conditional_t is false.
You need a can_apply_t that directly return Template<Args...> when possible, void otherwise.
I propose something as the following
template <template <typename...> class C, typename... Ts>
auto can_apply_f (int)
-> std::remove_reference_t<decltype(std::declval<C<Ts...>>())>;
template <template <typename...> class C, typename... Ts>
auto can_apply_f (long) -> void;
template <template <typename...> class C, typename ... Ts>
using can_apply_t = decltype(can_apply_f<C, Ts...>(0));
template <template<typename...> typename Template, typename... Args>
struct test {
using type = can_apply_t<Template, Args...>;
};
If you can use C++20, concepts will make things much easier.
template<template<typename...> typename, typename...>
struct test {
using type = void;
};
template<template<typename...> typename Template, typename... Args>
requires requires { typename Template<Args...>; }
struct test<Template, Args...> {
using type = Template<Args...>;
};
Demo.
Suppose I have a class with the following signature:
template <typename T, typename... Args>
class A;
But how this class behaves should depend on some other parameter, let's say it's the value of T::value:
template <typename T, typename... Args, typename Enable>
class A;
template <typename T, typename... Args, typename = typename std::enable_if<T::value>::type>
class A
{
// do something
};
template <typename T, typename... Args, typename = typename std::enable_if<!T::value>::type>
class A
{
// do something else
};
int main() { return 0; }
However, this program gives the following error:
prog.cpp:6:11: error: parameter pack ‘Args’ must be at the end of the
template parameter list
class A;
I have struggled to find a good source of information on the use of enable_if to select classes with variadic templates. The only question I could find is this one:
How to use std::enable_if with variadic template
But despite the name, this question and its answers aren't much help. If someone could provide or link a guide on how this should be approached and why that would be appreciated.
First of all, what you're trying is writing multiple definitions of a class template. That's not allowed because it violates One definition rule. If you want to do conditional enabling with classes, you need specializations. Also, the compiler error message already told you, you can't have a variadic parameter pack in the middle of a parameter list.
One way to do it would be:
namespace detail {
template<typename T, typename Enable, typename... Args>
class A_impl;
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<T::value>::type, Args...> {
// code here
};
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<!T::value>::type, Args...> {
// code here
};
}
template<typename T, typename...Args>
class A : public detail::A_impl<T, void, Args...> {};
Jonathan's way is also perfectly fine if the condition is really a bool, but it might not be useful if you wish to add more specializations that each depend on several conditons.
It looks as though for your purposes you don't need to enable/disable the class, you just need a partial specialization:
template <typename T, bool B = T::value, typename... Args>
class A;
template <typename T, typename... Args>
class A<T, true, Args...>;
template <typename T, typename... Args>
class A<T, false, Args...>;
I have traits classes sprinkled about my code which follow the same basic idiom:
template<class Frame, typename = void>
struct frame_traits
{
typedef void base_frame_type;
};
template<class Frame>
struct frame_traits<Frame, typename std::void_t<
typename Frame::base_frame_type>::type>
{
typedef typename Frame::base_frame_type base_frame_type;
};
and I have a bunch of trait checkers which use them, which also follow a similar idiom:
template <typename T>
struct has_base_frame_type : std::integral_constant<bool,
!std::is_same<typename frame_traits<T>::base_frame_type, void>::value>::type {};
however, it turns out that has_base_frame_type has become useful to multiple concepts in my code, and I'd like to generalize it further so that I can pass the traits class as an additional parameter:
template <typename T, template<typename> class Traits = frame_traits>
struct has_base_frame_type : std::integral_constant<bool,
!std::is_same<typename Traits<T>::base_frame_type, void>::value>::type {};
This doesn't work though, since templates with default arguments cannot be used as template template parameters.
I know I could work around the problem if I always use a traits class in the template instantiation (and modify the trait checker to accept it), namely
has_base_frame_type<frame_traits<MyClass>>::value
but I don't want to do that, because it would be all too easy to forget and pass in a non-trait class. In fact, that's how I originally had the code written until I forgot the trait one too many times and refactored it.
Is there someway I can modify my trait class idiom to work around the template template parameter problem?
Framework:
#include <type_traits>
template <typename...>
using void_t = void;
template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
struct detect_impl : std::false_type {};
template <template <typename...> class Operation, typename... Args>
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};
template <template <typename...> class Operation, typename... Args>
using detect = detect_impl<void, Operation, Args...>;
Detectors:
template <class Frame>
using frame_traits = typename Frame::base_frame_type;
template <class Frame>
using other_frame_traits = typename Frame::other_frame_type;
Trait with a default detector:
template <typename T, template <typename...> class Traits = frame_traits>
using has_frame_type = detect<Traits, T>;
Test:
struct A
{
using base_frame_type = void;
};
struct B
{
using other_frame_type = void;
};
int main()
{
static_assert(has_frame_type<A>{}, "!"); // default
static_assert(!has_frame_type<B>{}, "!"); // default
static_assert(!has_frame_type<A, other_frame_traits>{}, "!"); // non-default
static_assert(has_frame_type<B, other_frame_traits>{}, "!"); // non-default
}
DEMO
Suppose I have a class with the following signature:
template <typename T, typename... Args>
class A;
But how this class behaves should depend on some other parameter, let's say it's the value of T::value:
template <typename T, typename... Args, typename Enable>
class A;
template <typename T, typename... Args, typename = typename std::enable_if<T::value>::type>
class A
{
// do something
};
template <typename T, typename... Args, typename = typename std::enable_if<!T::value>::type>
class A
{
// do something else
};
int main() { return 0; }
However, this program gives the following error:
prog.cpp:6:11: error: parameter pack ‘Args’ must be at the end of the
template parameter list
class A;
I have struggled to find a good source of information on the use of enable_if to select classes with variadic templates. The only question I could find is this one:
How to use std::enable_if with variadic template
But despite the name, this question and its answers aren't much help. If someone could provide or link a guide on how this should be approached and why that would be appreciated.
First of all, what you're trying is writing multiple definitions of a class template. That's not allowed because it violates One definition rule. If you want to do conditional enabling with classes, you need specializations. Also, the compiler error message already told you, you can't have a variadic parameter pack in the middle of a parameter list.
One way to do it would be:
namespace detail {
template<typename T, typename Enable, typename... Args>
class A_impl;
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<T::value>::type, Args...> {
// code here
};
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<!T::value>::type, Args...> {
// code here
};
}
template<typename T, typename...Args>
class A : public detail::A_impl<T, void, Args...> {};
Jonathan's way is also perfectly fine if the condition is really a bool, but it might not be useful if you wish to add more specializations that each depend on several conditons.
It looks as though for your purposes you don't need to enable/disable the class, you just need a partial specialization:
template <typename T, bool B = T::value, typename... Args>
class A;
template <typename T, typename... Args>
class A<T, true, Args...>;
template <typename T, typename... Args>
class A<T, false, Args...>;
I am attempting to build a variadically templated class. As is common, each level of the instantiation needs to instantiate the "next level" by slicing off one type and then using the remainder. For my final level, rather than specialize on one type, I'd rather give some base case type and keep from duplicating the actual logic.
I've added a std::conditional to switch on the BaseCase when the rest of the types consists of an empty parameter pack.
class BaseCase { };
template <typename T, typename... Ts>
class VariadicClass;
template <typename... Ts>
using NextLevel = typename std::conditional<
sizeof...(Ts) != 0, VariadicClass<Ts...>, BaseCase>::type;
template <typename T, typename... Ts>
class VariadicClass {
T this_level; // whatever
NextLevel<Ts...> next_level; // fails when Ts is empty
};
The problem is that VariadicClass is templated on at least one type parameter, so when it hits the base case (Ts is empty), trying to use std::conditional uses VariadicClass<>, which fails of course.
The solution I've managed is to write some specific functions and use decltype along with overloads, and not use std::conditional at all.
template <typename... Ts>
VariadicClass<Ts...> type_helper(Ts&&...);
BaseCase type_helper();
template <typename... Ts>
using NextLevel = decltype(type_helper(std::declval<Ts>()...));
Now, this works, but if I want to keep up this practice every time I have a variadic class, it seems tedious. Is there a way to use std::conditional or something similar to achieve this effect without having to write out so much problem-specific code?
Defer evaluation.
template<class T>struct identity{
template<class...>using result=T;
};
template<template<class...>class src>
struct delay{
template<class...Ts>using result=src<Ts...>;
};
template <typename... Ts>
using NextLevel =
typename std::conditional<
sizeof...(Ts) != 0, delay<VariadicClass>, identity<BaseCase>
>::type::template result<Ts...>;
identity ignores the Ts... and returns its argument. delay takes a template and applies the Ts.... While the signature looks suspicious, it works.
Why not just
class BaseCase { };
template <typename... Ts>
class VariadicClass; // undefined base template
template <typename... Ts>
using NextLevel = typename std::conditional<
sizeof...(Ts) != 0, VariadicClass<Ts...>, BaseCase>::type;
template <typename T, typename... Ts>
class VariadicClass<T, Ts...> { // partial specialization for having at least 1 type parameter
T this_level; // whatever
NextLevel<Ts...> next_level;
};
After reading T.C.'s answer and Yakk's comment, I realized I could write this as one templated class with two specializations, rather than write another BaseClass and the type alias.
template <typename... Ts>
class VariadicClass;
// specialization gets everything but an empty Ts
template <typename T, typename... Ts>
class VariadicClass<T, Ts...> {
VariadicClass<Ts...> next_level;
// normal case
};
template <>
class VariadicClass<> { // instead of class BaseCase
// base case
};
Alternatively, you may specialize VariadicClass<T>
class BaseCase {};
// general case
template <typename T, typename... Ts>
class VariadicClass {
T this_level; // whatever
VariadicClass<Ts...> next_level;
};
// specialization
template <typename T>
class VariadicClass<T> {
T this_level; // whatever
BaseClass next_level;
};