Reputation: 35
I'm toying with SFINAE and I try to generate a clean failure for three cases. The intend of UseInput
is to switch an Input
from IsFree
to IsUsed
. The Input
types are packed inside an Inputs
type (some kind of tuple). We access an Input
by its index.
struct IsFree
{
};
struct IsUsed
{
};
template <typename Type, typename State>
struct Input
{
};
template <typename... DefaultInputs>
struct Inputs
{
};
template <typename Type, typename... DefaultInputs>
struct Inputs<Input<Type, IsFree>, DefaultInputs...>
: public Inputs<DefaultInputs...>
{
};
template <typename Type, typename... DefaultInputs>
struct Inputs<Input<Type, IsUsed>, DefaultInputs...>
: public Inputs<DefaultInputs...>
{
};
// UseInput creates a new Inputs type where an Input state is switched from IsFree to IsUsed
template <int index, typename LastInputs, typename NextInputs>
struct UseInput;
// We found the Input to switch
template <typename Type, typename... LastInputs, typename... NextInputs>
struct UseInput<0, Inputs<Input<Type, IsFree>, LastInputs...>, Inputs<NextInputs...>>
: public UseInput<-1, Inputs<LastInputs...>, Inputs<NextInputs..., Input<Type, IsUsed>>>
{
};
// We try to switch an Input we shouldn't, this is an error
template <typename Type, typename... LastInputs, typename... NextInputs>
struct UseInput<0, Inputs<Input<Type, IsUsed>, LastInputs...>, Inputs<NextInputs...>>
{
};
// We processed the Inputs
template <int index, typename... NextInputs>
struct UseInput<index, Inputs<>, Inputs<NextInputs...>>
{
typedef Inputs<NextInputs...> Result;
};
// Recursively process each Input
template <int index, typename Type, typename State, typename... LastInputs, typename... NextInputs>
struct UseInput<index, Inputs<Input<Type, State>, LastInputs...>, Inputs<NextInputs...>>
: public UseInput<index - 1, Inputs<LastInputs...>, Inputs<NextInputs..., Input<Type, State>>>
{
};
These use cases work :
typedef Inputs<Input<float, IsFree>, Input<float, IsUsed>, Input<float, IsFree>> TestInputs;
typedef UseInput<0, TestInputs, Inputs<>>::Result Result0;
static_assert(std::is_same<Result0, Inputs<Input<float, IsUsed>, Input<float, IsUsed>, Input<float, IsFree>>>::value, "Unexpected type...");
typedef UseInput<2, TestInputs, Inputs<>>::Result Result2;
static_assert(std::is_same<Result2, Inputs<Input<float, IsFree>, Input<float, IsUsed>, Input<float, IsUsed>>>::value, "Unexpected type...");
These must fail :
typedef Inputs<Input<float, IsFree>, Input<float, IsUsed>, Input<float, IsFree>> TestInputs;
typedef UseInput<1, TestInputs, Inputs<>>::Result Result1; // Already used Input
typedef UseInput<3, TestInputs, Inputs<>>::Result Result3; // Bad index
typedef UseInput<-1, TestInputs, Inputs<>>::Result Result4; //
The only solution I found is omit the typedef Inputs<> Result;
but this seems bad way to report the errors and requires an additional index for testing the indices.
How can I fix my templates if I want a clean error reporting by the compiler ?
Upvotes: 1
Views: 98
Reputation: 393134
I'd treat the list as a list, and iterate using the indices trick + parameter pack expansion.
Finally, a simple check that the resulting type isn't the same as the original will cover the case of "using" an input that was already used:
template <typename Type, typename State> struct Input {};
template <typename Type> using Used = Input<Type, struct IsUsed>;
template <typename Type> using Free = Input<Type, struct IsFree>;
template <typename... Input> struct InputList {};
Let's first make type functions to change state:
namespace detail {
template <typename Type, typename State> struct ChangeStateF;
template <typename Type, typename State> struct ChangeStateF<Input<Type, IsFree>, State> { using type = Input<Type, State>; };
template <typename Type, typename State> struct ChangeStateF<Input<Type, IsUsed>, State> { using type = Input<Type, State>; };
Which we can alias for convenience:
template <typename Input> using UseInput = ChangeStateF<Input, IsUsed>;
template <typename Input> using FreeInput = ChangeStateF<Input, IsFree>;
Now all we need is to conditionally apply such a change
template <bool C, template <typename...> class F, typename T>
using apply_if = std::conditional<C, typename F<T>::type, T>;
to all the elements in an InputList
by index:
template<size_t N, template <typename...> class F, typename... I, size_t... Idx>
constexpr auto apply_at(InputList<I...>, std::index_sequence<Idx...>) {
return InputList<typename apply_if<Idx == N, F, I>::type...>{};
}
The index_sequence
is a trivial type included (with C++14) and we can forward from an overload without it:
template<size_t N, template <typename...> class F, typename... I>
constexpr auto apply_at(InputList<I...> l) {
return apply_at<N, F>(l, std::index_sequence_for<I...>{});
}
Now to detect indices out of bounds or changes that didn't have any effect (e.g. the input was already in the required state):
template<size_t N, template <typename...> class F, typename... I>
constexpr auto mutate_at(InputList<I...> l) {
auto r = apply_at<N, F>(l);
static_assert(not std::is_same<decltype(r), decltype(l)>{}, "Invalid Mutation");
return r;
}
This is 'modern style' meta programming, let's leave the detail namespace:
template<size_t N, typename L>
constexpr auto use_at(L x) { return mutate_at<N, UseInput>(x); }
template<size_t N, typename L>
constexpr auto free_at(L x) { return mutate_at<N, FreeInput>(x); }
}
Now, we're back in "type land":
template <std::size_t N, typename List> using Use = decltype(detail::use_at<N>(List{}));
template <std::size_t N, typename List> using Clear = decltype(detail::free_at<N>(List{}));
#include <iostream>
#include <typeinfo>
int main() {
using Inputs = InputList<Free<float>, Used<float>, Used<float> >;
using New = Use<0, Inputs>;
//using Oops = Use<0, New>; doesn't compile
std::cout << typeid(Inputs).name() << "\n";
std::cout << typeid(New).name() << "\n";
}
Prints
InputList<Input<float, IsFree>, Input<float, IsUsed>, Input<float, IsUsed> >
InputList<Input<float, IsUsed>, Input<float, IsUsed>, Input<float, IsUsed> >
Uncommenting the line with Oops
would lead to
main.cpp:36:9: error: static_assert failed "Invalid Mutation"
static_assert(not std::is_same<decltype(r), decltype(l)>{}, "Invalid Mutation");
Upvotes: 1