Variadic alias template to non-variadic class template - c++

In trying to write a simple example for currying of metafunction classes, I wrote the following:
#include <type_traits>
struct first {
template <typename T, typename U>
using apply = T;
};
template <typename C, typename... Args>
struct curry {
struct type {
template <typename... OtherArgs>
using apply = typename C::template apply<Args..., OtherArgs...>;
};
};
int main() {
static_assert(std::is_same<first::apply<int, char>, int>::value, ""); // OK
using AlwaysInt = curry<first, int>::type;
static_assert(std::is_same<AlwaysInt::apply<char>, int>::value, ""); // error
}
The second static_assert fails to compile on both gcc 5.1:
main.cpp:17:72: error: pack expansion argument for non-pack parameter 'U' of alias template 'template<class T, class U> using apply = T'
using apply = typename C::template apply<Args..., OtherArgs...>;
^
and clang 3.6:
main.cpp:17:59: error: pack expansion used as argument for non-pack parameter of alias template
using apply = typename C::template apply<Args..., OtherArgs...>;
^~~~~~~~~~~~
Same error in both cases. However, if I outsource the application in curry to a separate metafunction:
template <typename C, typename... Args>
struct eval {
using type = typename C::template apply<Args...>;
};
template <typename C, typename... Args>
struct curry {
struct type {
template <typename... OtherArgs>
using apply = typename eval<C, Args..., OtherArgs...>::type;
};
};
Both compilers compile just fine. Is there something wrong with the original example or is this just a bug in both compilers?

Related

Finding if a class template can be instantiated with a set of arguments, arity-wise (in C++17)

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.

Why does MSVC fail to compile this CRTP code?

I wrote some code that compiles well on recent versions of GCC and Clang, but fails on MSVC:
invalid template argument for template parameter 'TL', expected a class template
Can anyone please explain is this a bug or have I misunderstood something?
Without partial specialization of types_list it works fine on MSVC too.
#include <cstdint>
#include <type_traits>
namespace detail
{
template <std::size_t Index, typename ...Ts>
struct get
{
static_assert(Index < sizeof...(Ts), "types_list::get index out of bounds");
private:
template <std::size_t CurrentIndex, typename ...Us>
struct helper
{
using type = void;
};
template <std::size_t CurrentIndex, typename U, typename ...Us>
struct helper<CurrentIndex, U, Us...>
{
using type = std::conditional_t<CurrentIndex == Index, U, typename helper<CurrentIndex + 1, Us...>::type>;
};
public:
using type = typename helper<0, Ts...>::type;
};
template <template <typename...> typename TL, typename ...Ts>
struct list_impl
{
inline static constexpr std::size_t size = sizeof...(Ts);
template <std::size_t Index>
using get = typename detail::get<Index, Ts...>::type;
};
}
template <typename ...Ts>
struct types_list : public detail::list_impl<types_list, Ts...>
{
};
template <typename T, typename ...Ts>
struct types_list<T, Ts...> : public detail::list_impl<types_list, T, Ts...>
{
private:
using impl = detail::list_impl<types_list, T, Ts...>;
public:
using front = typename impl:: template get<0>;
using back = typename impl:: template get<impl::size - 1>;
};
using t = types_list<int, double>::front;
using t2 = types_list<int, double>::back;
using t3 = types_list<int, char, double>::get<1>;
int main()
{
t x = 10;
t2 y = 1.4;
t3 z = 'a';
}
EDIT: More detailed example https://pastebin.com/snRC0EPi
It seems to be bug indeed. To refer to the current instantiation you can usually omit the template paramter of the current type. This is what MSVC seems to do here. It causes an error because the template expects a template template parameter.
But why you want to use a template template parameter? For CRTP you use the "bound template type".
Update
If you need to instantiate the template with new parameters this can be done easily with a helper type:
namespace helper{
template<
typename VariadicType
>
class GetTemplateOfVariadicType{
};
template<
template <typename...> typename Template,
typename... Ts
>
class GetTemplateOfVariadicType<Template<Ts...>>
{
public:
template<typename... T>
using type = Template<T...>;
};
}
Usage:
using OtherTL = typename ::helper::GetTemplateOfVariadicType<TL>::template type<int, bool, char>;
See here: godbolt

Error with MSVC only: use of class template requires template argument list

This compiles fine without errors on both clang and gcc, but fails with MSVC: (compiler explorer)
// detection idiom
template <class...>
using void_t = void;
template <class Void, template <class...> class Op, class... Args>
struct detector_base
{
using type = void;
};
template <template <class...> class Op, class... Args>
struct detector_base<void_t<Op<Args...>>, Op, Args...>
{
using type = Op<Args...>;
};
template <class T>
using check = decltype(T::x);
template <typename T>
struct type {};
using dt = typename detector_base<void, check, int>::type; // this is void
using t = type<dt>;
// ^--- 't': use of class template requires template argument list
This occurs when using the detected type with any template type. Is this a compiler bug, or is due to lack of support (if so, for what)?

Use enable_if with is_integral to make distribution traits

I want to make a traits for std::uniform_*_distribution according to type given. E.g.:
distribution_traits<float>::type int_dist;
I tried following ways, but none of them compiles, and I don't know why.
Implementation 1
Use std::enable_if with typedefs:
template <typename T>
struct distribution_traits {
using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
using type = typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
};
Clang 3.4 complains:
dist_traits.cpp:7:9: error: redefinition of 'type'
using type = typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
^
dist_traits.cpp:6:9: note: previous definition is here
using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
^
dist_traits.cpp:6:40: error: no type named 'type' in 'std::enable_if<false, std::uniform_int_distribution<float> >'; 'enable_if' cannot be used to
disable this declaration
using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
^~~~~~~~~~~~~~~~~~~~~~~~~~
dist_traits.cpp:28:3: note: in instantiation of template class 'distribution_traits<float>' requested here
distribution_traits<float>::type int_dist;
^
2 errors generated.
Implementation 2
Use enable_if as class template parameter:
template <typename T, typename distribution_t = void>
struct distribution_traits;
template <typename T>
struct distribution_traits<
T, typename std::enable_if<std::is_integral<T>::value,
std::uniform_int_distribution<T> >::type > {
using type = std::uniform_int_distribution<T>;
};
template <typename T>
struct distribution_traits<
T, typename std::enable_if<std::is_floating_point<T>::value,
std::uniform_real_distribution<T> >::type > {
using type = std::uniform_real_distribution<T>;
};
And Clang complains
dist_traits.cpp:28:3: error: implicit instantiation of undefined template 'distribution_traits<float, void>'
distribution_traits<float>::type int_dist;
^
Either way cannot be compiled by MSVC++ 12.0, and the error messages are similar.
Could anyone please explain what's wrong I'm doing with SFINAE? Thanks!
For those who are curious about solution, here is the one that compiles:
template <typename T>
auto dist() -> typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
template <typename T>
auto dist() -> typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
template <typename T>
struct distribution_traits {
using type = decltype(dist<T>());
};
BTW, if put dist function into distribution_traits, the compilation will fail with error: function only differs in return type cannot be overloaded. :(
SFINAE can be used to discard overloads of function templates and class template specializations during substitution of template arguments.
It cannot be used with type/template aliases like you're trying to do.
About your working code - putting dist inside the class doesn't work because you attempt to call dist inside decltype without an object. Make dist static and it'll work:
template <typename T>
struct distribution_traits {
template <typename U>
static auto dist() -> typename std::enable_if<std::is_integral<U>::value, std::uniform_int_distribution<U>>::type;
template <typename U>
static auto dist() -> typename std::enable_if<std::is_floating_point<U>::value, std::uniform_real_distribution<U>>::type;
using type = decltype(dist<T>());
};
For implementation 2 to work, you need to omit the second argument of enable_if:
template
struct distribution_traits;
template <typename T>
struct distribution_traits<
T, typename std::enable_if<std::is_integral<T>::value>::type> {
using type = std::uniform_int_distribution<T>;
};
otherwise the specialization you define is distribution_traits<T, uniform_int_distribution<T>> and that doesn't match an instantiation like distribution_traits<float> because the second parameter is defaulted to void.

Attempt to remove last type from a tuple is failing

I'm trying to remove the last element of a tuple. It works when I have only one element in the tuple to remove. But when I have more than one, things go wrong. I can't get why this isn't working. These are the errors I'm getting:
prog.cpp: In function ‘int main()’:
prog.cpp:24:22: error: incomplete type ‘remove_last<std::tuple<int, int> >’ used in nested name specifier
prog.cpp:24:22: error: incomplete type ‘remove_last<std::tuple<int, int> >’ used in nested name specifier
prog.cpp:24:70: error: template argument 1 is invalid
#include <tuple>
#include <type_traits>
template <class T>
struct remove_last;
template <class T>
struct remove_last<std::tuple<T>>
{
using type = std::tuple<>;
};
template <class... Args, typename T>
struct remove_last<std::tuple<Args..., T>>
{
using type = std::tuple<Args...>;
};
int main()
{
std::tuple<int, int> var;
static_assert(
std::is_same<remove_last<decltype(var)>::type,
std::tuple<int>>::value, "Values are not the same"
);
}
The errors go away when I make the template argument non-variadic in one of the specializations. But then that becomes a specialization which will only process a tuple with two elements - not what I was aiming for. How can I get this to work with variadic arguments? In other words, how can I get this to work when there is more than one element in the tuple?
The problem is that the argument pack is greedy and - since it comes first - eats up all the types in the sequence when performing type deduction, including the T you expect to be left out of Args....
You could define the variadic specialization this way (notice that the argument pack is now appearing last in std::tuple<T, Args...>):
template <class T, class... Args>
struct remove_last<std::tuple<T, Args...>>
{
using type = typename concat_tuple<
std::tuple<T>,
typename remove_last<std::tuple<Args...>>::type
>::type;
};
And have the concat_tuple meta-function defined this way:
template<typename, typename>
struct concat_tuple { };
template<typename... Ts, typename... Us>
struct concat_tuple<std::tuple<Ts...>, std::tuple<Us...>>
{
using type = std::tuple<Ts..., Us...>;
};
An alternative solution, requires C++14 or newer:
#include <tuple>
template<class Tuple>
struct remove_last;
template<>
struct remove_last<std::tuple<>>; // Define as you wish or leave undefined
template<class... Args>
struct remove_last<std::tuple<Args...>>
{
private:
using Tuple = std::tuple<Args...>;
template<std::size_t... n>
static std::tuple<std::tuple_element_t<n, Tuple>...>
extract(std::index_sequence<n...>);
public:
using type = decltype(extract(std::make_index_sequence<sizeof...(Args) - 1>()));
};
template<class Tuple>
using remove_last_t = typename remove_last<Tuple>::type;
See https://en.cppreference.com/w/cpp/utility/integer_sequence