SFINAE Template Specialization in Separate Source File - c++

I am working maintenance on a program with a bunch of different structures that are fundamentally similar. I want to write a template method that utilizes SFINAE to enable calling this method from the client. When I define the template specialization inline, everything works as expected. However, when I try to move the template definition into a separate compilation unit, I run into issues. I am trying to move the template implementation into a separate file in order to enable using forward declarations for most of the dependent classes. The following is an example of what I am attempting to achieve:
// Impl.h
#pragma once
struct A {
struct {
int value;
} valueA;
};
struct B {
struct {
int value;
} valueB;
};
template<typename T>
int GetValue(T const &value);
// Impl.cpp
#include "Impl.h"
#include <type_traits>
using std::enable_if_t;
using std::remove_reference_t;
template<typename T, typename U, U(T::*Value)>
static inline int GetValueImpl(T const &value) {
return (value.*Value).value;
}
template<typename T>
enable_if_t<T::valueA, int> GetValue(T const &value) {
static constexpr auto T::*const Value = &T::valueA;
typedef remove_reference_t<decltype(value.*Value)> ValueType;
return GetValueImpl<T, ValueType, Value>(value);
}
template<typename T>
enable_if_t<T::valueB, int> GetValue(T const &value) {
static constexpr auto T::*const Value = &T::valueB;
typedef remove_reference_t<decltype(value.*Value)> ValueType;
return GetValueImpl<T, ValueType, Value>(value);
}
template<> int GetValue(A const &); // C2912 here
template<> int GetValue(B const &); // C2912 here
I am using VS2017u2, and am getting error C2912: explicitt specialization 'int GetValue(const A &)' is not a specialization of a function template. Does anyone know how to make this work with the definitions in a separate compilation unit?

When you write enable_if_t<T::valueA, int>, it is checking for a static member of T called valueA (Which would presumably be a static constexpr bool valueA = /* true or false */;, like what T::value would mean if T was using T = std::is_same<U, V>; ).
To actually check if it has a member called valueA or valueB, put it in a context where there would be a substitution error if the member didn't exist, or true. Something like:
// Pointers to member variables can never be null
// so these will always be enabled if `valueA` or
// `valueB` exist in the first place
enable_if_t<&T::valueA != nullptr, int>
enable_if_t<&T::valueB != nullptr, int>
// But that also allows pointers to static members
// so if you don't want that, you can do something else.
// Like checking if `&T::member` is a pointer to a member
// (But this is probably overkill as you have a specific set
// of types and none of those names are ever static members
// and if you didn't there is a small caveat with
// an overloaded `operator&` but that doesn't really matter)
enable_if_t<std::is_same_v<decltype(&T::valueA), decltype(T::valueA) T::*>, int>
enable_if_t<std::is_same_v<decltype(&T::valueB), decltype(T::valueB) T::*>, int>
Even after fixing the SFINAE check, you are using the wrong syntax for template instantiation. You should use:
template int GetValue(A const &); // No `<>`
template int GetValue(B const &);
And after that, it still doesn't work because template<typename T> enable_if_t<..., int> GetValue(T const&); and template<typename T> int GetValue(T const&); are different functions, so it is ambiguous which one it should instantiate (as both would work). You need to make both of those into different functions (GetValueImpl2 in my example) and have GetValue declared the same way as in the header:
#include <type_traits>
#include "Impl.h"
using std::enable_if_t;
using std::remove_reference_t;
template<typename T, typename U, U(T::*Value)>
static inline int GetValueImpl(T const &value) {
return (value.*Value).value;
}
template<typename T>
enable_if_t<std::is_same_v<decltype(&T::valueA), decltype(T::valueA) T::*>, int> GetValueImpl2(T const &value) {
static constexpr auto T::*const Value = &T::valueA;
using ValueType = decltype(T::valueA);
return GetValueImpl<T, ValueType, Value>(value);
}
template<typename T>
enable_if_t<std::is_same_v<decltype(&T::valueB), decltype(T::valueB) T::*>, int> GetValueImpl2(T const &value) {
static constexpr auto T::*const Value = &T::valueB;
using ValueType = decltype(T::valueB);
return GetValueImpl<T, ValueType, Value>(value);
}
template<typename T>
int GetValue(T const&value) {
return GetValueImpl2(value);
}
template int GetValue<A>(A const &);
template int GetValue<B>(B const &);

Related

C++ wrapper around any collection type using templates

Extremely new to c++ however have a question regarding templates
Suppose I have a simple template class as defined below:
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
c.push_back(value);
}
};
The aim of the class being to accept any type of collection, and allow a user to insert the correct type of value for the specified typename Collection.
The obvious problem is that this is only going to work for types which have a push_back method defined, which means it would work with list however not with set.
I started reading about template specialization to see if that'd be any help, however I don't think this would provide a solution as the type contained within the set would have to be known.
How would this problem be approached in c++?
You can use std::experimental::is_detected and if constexpr to make it work:
template<class C, class V>
using has_push_back_impl = decltype(std::declval<C>().push_back(std::declval<V>()));
template<class C, class V>
constexpr bool has_push_back = std::experimental::is_detected_v<has_push_back_impl, C, V>;
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
if constexpr (has_push_back<Collection, value_type>) {
std::cout << "push_back.\n";
c.push_back(value);
} else {
std::cout << "insert.\n";
c.insert(value);
}
}
};
int main() {
MySack<std::set<int>> f;
f.add(23);
MySack<std::vector<int>> g;
g.add(23);
}
You can switch to insert member function, which has the same syntax for std::vector, std::set, std::list, and other containers:
void add(const value_type& value) {
c.insert(c.end(), value);
}
In C++11, you might also want to create a version for rvalue arguments:
void add(value_type&& value) {
c.insert(c.end(), std::move(value));
}
And, kind-of simulate emplace semantics (not truly in fact):
template <typename... Ts>
void emplace(Ts&&... vs) {
c.insert(c.end(), value_type(std::forward<Ts>(vs)...));
}
...
int main() {
using value_type = std::pair<int, std::string>;
MySack<std::vector<value_type>> v;
v.emplace(1, "first");
MySack<std::set<value_type>> s;
s.emplace(2, "second");
MySack<std::list<value_type>> l;
l.emplace(3, "third");
}
I started reading about template specialization to see if that'd be
any help, however I don't think this would provide a solution as the
type contained within the set would have to be known.
You can partially specialize MySack to work with std::set.
template <class T>
class MySack<std::set<T>> {
//...
};
However, this has the disadvantage that the partial specialization replaces the whole class definition, so you need to define all member variables and functions again.
A more flexible approach is to use policy-based design. Here, you add a template parameter that wraps the container-specific operations. You can provide a default for the most common cases, but users can provide their own policy for other cases.
template <class C, class V = typename C::value_type>
struct ContainerPolicy
{
static void push(C& container, const V& value) {
c.push_back(value);
}
static void pop(C& container) {
c.pop_back();
}
};
template <class C, class P = ContainerPolicy<C>>
class MySack
{
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
P::push(c, value);
}
};
In this case, it is easier to provide a partial template specialization for the default policy, because it contains only the functionality related to the specific container that is used. Other logic can still be captured in the MySack class template without the need for duplicating code.
Now, you can use MySack also with your own or third party containers that do not adhere to the STL style. You simply provide your own policy.
struct MyContainer {
void Add(int value);
//...
};
struct MyPolicy {
static void push(MyContainer& c, int value) {
c.Add(value);
}
};
MySack<MyContainer, MyPolicy> sack;
If you can use at least C++11, I suggest the creation of a template recursive struct
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
to manage precedence in case a container can support more than one adding functions.
So you can add, in the private section of your class, the following template helper functions
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
Observe that the decltype() part enable they (through SFINAE) only if the corresponding method (push_back(), insert() or push_front()) is enabled.
Now you can write add(), in the public section, as follows
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
The tag<2> element make so the tag<2> addHelper() method is called, if available (if push_back() is available for type C), otherwise is called the tag<1> method (the insert() one) if available, otherwise the tag<0> method (the push_front() one) is available. Otherwise error.
Also observe the T && t and std::forward<T>(t) part. This way you should select the correct semantic: copy or move.
The following is a full working example
#include <map>
#include <set>
#include <list>
#include <deque>
#include <vector>
#include <iostream>
#include <forward_list>
#include <unordered_map>
#include <unordered_set>
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
template <typename C>
class MySack
{
private:
C c;
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
public:
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
};
int main ()
{
MySack<std::vector<int>> ms0;
MySack<std::deque<int>> ms1;
MySack<std::set<int>> ms2;
MySack<std::multiset<int>> ms3;
MySack<std::unordered_set<int>> ms4;
MySack<std::unordered_multiset<int>> ms5;
MySack<std::list<int>> ms6;
MySack<std::forward_list<int>> ms7;
MySack<std::map<int, long>> ms8;
MySack<std::multimap<int, long>> ms9;
MySack<std::unordered_map<int, long>> msA;
MySack<std::unordered_multimap<int, long>> msB;
ms0.add(0);
ms1.add(0);
ms2.add(0);
ms3.add(0);
ms4.add(0);
ms5.add(0);
ms6.add(0);
ms7.add(0);
ms8.add(std::make_pair(0, 0L));
ms9.add(std::make_pair(0, 0L));
msA.add(std::make_pair(0, 0L));
msB.add(std::make_pair(0, 0L));
}

restricting the types using templates in vc++

As far my understanding goes I want to restrict only 2 types int and string for the following class, just like in java template definition with extends.
template<typename T>
typename std::enable_if<std::is_same<T,int>::value || std::is_same<T,string>::value>::type
class ExchangeSort
{
public:
void bubbleSort(T buffer[], int s);
void print(T buffer[], int s);
void bubbleSort(const vector<T>& vec);
};
But if I'm instantiating like below
ExchangeSort<int>* sortArray = new ExchangeSort<int>;
I'm getting errors for the above line ExchangeSort is undefined. What is the problem ?
SFINAE can be used to conditionally disable function overloads or template specialisations. It makes no sense to try to use it to disable a class template, since class templates cannot be overloaded. You'll be better off using a static assertion:
template<typename T>
class ExchangeSort
{
static_assert(std::is_same<T,int>::value || std::is_same<T,string>::value, "ExchangeSort requires int or string");
public:
// The rest as before
As to the errors you were getting, the code didn't make syntactic sense. enable_if can only appear where a type is expected. With functions, it's often used to wrap the return type, in which case it's syntactically similar to what you wrote. But with classes, there's no type between template and the class definition.
Here's another way which allows the possibility of adding further specialisations down the line:
#include <type_traits>
#include <vector>
#include <string>
//
// Step 1 : polyfill missing c++17 concepts
//
namespace notstd {
template<class...> struct disjunction : std::false_type { };
template<class B1> struct disjunction<B1> : B1 { };
template<class B1, class... Bn>
struct disjunction<B1, Bn...>
: std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> { };
}
//
// Step 2 : create a test to see if a type is in a list
//
namespace detail {
template<class T, class...Us>
struct is_one_of : notstd::disjunction< std::is_same<T, Us>... > {};
}
template<class T, class...Us>
static constexpr auto IsOneOf = detail::is_one_of<T, Us...>::value;
//
// Step 3 : declare the default specialisation, but don't define it
//
template<class T, typename Enable = void>
struct ExchangeSort;
//
// Step 4 : define the specialisations we want
//
template<typename T>
class ExchangeSort<T, std::enable_if_t<IsOneOf<T, int, std::string>>>
{
public:
void bubbleSort(T buffer[], int s);
void print(T buffer[], int s);
void bubbleSort(const std::vector<T>& vec);
};
//
// Test
//
int main()
{
auto esi = ExchangeSort<int>();
auto ess = ExchangeSort<std::string>();
// won't compile
// auto esd = ExchangeSort<double>();
}

c++ Templates to change constness of a function

I am interested in designing a template interface where the const ness of the function and return type itself changes depending on the template parameter. I have managed to do this for the return type as follows.
template<typename T, bool canChange>
struct Changable{};
template<typename T>
struct Changable<T,true>
{
typedef T type;
};
template<typename T>
struct Changable<T,false>
{
typedef const T type;
};
template<typename T, bool canChange>
struct Data{
typedef typename Changable<T,canChange>::type DataType;
DataType m_data; //< This makes it const/non-const at compile time.
// This function will also make the return type const/non-const
// at compile time.
DataType& GetDataRef(){ return m_data;}
//However, it seems to me that I still need a second function
//with an explicit "const", which I can't seem to avoid.
DataType& GetDataRef()const{return m_data;}
};
Can I somehow avoid having two const/non-const functions here at compile time using some SFINAE magic? std::enable_if would have been ideal here but it seems to me that const is not a type and that approach may not work. Any suggestions?
Here is an example based on inheritance:
#include <type_traits>
#include <iostream>
template<typename T, bool canChange>
struct Changable { using type = const T; };
template<typename T>
struct Changable<T, true> { using type = std::decay_t<T>; };
template<typename, typename, bool>
struct Base;
template<typename D, typename T>
struct Base<D, T, true> {
using DataType = typename Changable<T, true>::type;
DataType& GetDataRef() { std::cout << "non-const" << std::endl; return static_cast<D*>(this)->m_data; }
};
template<typename D, typename T>
struct Base<D, T, false> {
using DataType = typename Changable<T, false>::type;
DataType& GetDataRef() const { std::cout << "const" << std::endl; return static_cast<const D*>(this)->m_data; }
};
template<typename T, bool canChange>
struct Data: Base<Data<T, canChange>, T, canChange> {
friend class Base<Data<T, canChange>, T, canChange>;
typename Base<Data<T, canChange>, T, canChange>::DataType m_data{};
using Base<Data<T, canChange>, T, canChange>::GetDataRef;
};
int main() {
Data<int, true> d1;
Data<int, false> d2;
d1.GetDataRef();
d2.GetDataRef();
}
As requested, Data has only one definition of the GetDataRef method.
Which one is available, the const one or the other one, depends on the value of canChange.
Note the friend declaration. It allows the base class to access to the private data members of Data.
I think I'd approach this using the templates already available in the standard library. It does not require inheritance or any custom classes.
#include <utility>
template<typename T, bool canChange>
struct Data{
using value_type = T;
using cv_type = std::conditional_t<canChange, value_type, std::add_const_t<value_type>>;
using reference = std::add_lvalue_reference_t<cv_type>;
using const_reference = std::add_lvalue_reference_t<std::add_const_t<cv_type>>;
Data(T t) : m_data(std::move(t)) {}
cv_type m_data; //< This makes it const/non-const at compile time.
// This function will also make the return type const/non-const
// at compile time.
reference GetDataRef(){ return m_data;}
//However, it seems to me that I still need a second function
//with an explicit "const", which I can't seem to avoid.
const_reference GetDataRef() const {return m_data;}
};
int main()
{
Data<int, true> d1 { 10 };
d1.m_data = 12;
const Data<int, true>& rd1 = d1;
auto& a = d1.GetDataRef();
auto& b = rd1.GetDataRef();
a = 12; // compiles fine
// b= 12; won't compile
Data<int, false> d2 { 10 };
const Data<int, false>& rd2 = d2;
auto& c = d2.GetDataRef();
auto& d = rd2.GetDataRef();
// c = 12; // won't compile
// d = 12; // won't compile
}
Now to the question:
Can I somehow avoid having two const/non-const functions here at compile time using some SFINAE magic?
You're almost answering your own question here. SFINAE requires that template arguments are considered in immediate context. Which is a complex way of saying that the expression in std::enable_if<> must depend on some template type.
Unhappily, the template type of T is known by the time the function GetDataRef is evaluated, so enable_if won't help us here.
So if we only want one version of GetDataRef we would indeed have to resort to derivation from a template type (the base class would then be evaluated in immediate context of T).
However, there is a problem even then.
consider:
Data<int, true>& x This is a reference to mutable container containing mutable data
const Data<int, true>& y This is a reference to an immutable container containing mutable data
calling x.GetDataRef() ought to return a mutable reference to an int, otherwise we'll confuse our users.
calling y.GetDataRef() should certainly return a const reference to an int, otherwise again, users may be shocked to learn that a member of a const thing is actually mutable.
Maybe something like this can solve the issue:
#include <type_traits>
#include <iostream>
template<typename T, bool canChange>
struct Changable: std::false_type { using type = const T; };
template<typename T>
struct Changable<T, true>: std::true_type { using type = std::decay_t<T>; };
template<typename T, bool canChange>
struct Data {
using DataTraits = Changable<T, canChange>;
private:
template<typename U>
std::enable_if_t<U::value, typename U::type&>
GetDataRefImpl() { std::cout << "non const" << std::endl; return m_data; }
template<typename U>
std::enable_if_t<not U::value, typename U::type&>
GetDataRefImpl() const { std::cout << "const" << std::endl; return m_data; }
public:
typename DataTraits::type m_data{};
typename DataTraits::type& GetDataRef() { return GetDataRefImpl<DataTraits>(); }
typename DataTraits::type& GetDataRef() const { return GetDataRefImpl<DataTraits>(); }
};
int main() {
Data<int, true> d1;
Data<int, false> d2;
d1.GetDataRef();
d2.GetDataRef();
}
The basic idea is to have both the functions exposed by the class, then forward them internally to the same sfinaed one that is const or non-const (this depends on the value of canChange).
As you can see by running the example, the result is:
non const
const
This is true even if both d1 and d2 have been defined as non const.
The std::enable_if turn on the right internal function at compile time.
Note that I've used what the C++14 offers (as an example std::enable_if_t).
The example can be easily converted to a C++11 based on (std::enable_if_t is nothing more than typename std::enable_if<condition, type>::type and so on).
If C++17 is available to you, have a look at is_const, add_const and remove_const.
Together with if constexpr (), a rather elegant solution should be possible.

Ambiguous template arguments not excluded by enable_if

I want to automatically choose the right pointer-to-member among overloaded ones based on the "type" of the member, by removing specializations that accept unconcerned members (via enable_if).
I have the following code:
class test;
enum Type
{
INT_1,
FLOAT_1,
UINT_1,
CHAR_1,
BOOL_1,
INT_2,
FLOAT_2,
UINT_2,
CHAR_2,
BOOL_2
};
template<typename T, Type Et, typename func> struct SetterOk { static const bool value = false; };
template<typename T> struct SetterOk<T,INT_1,void (T::*)(int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,FLOAT_1,void (T::*)(float)> { static const bool value = true; };
template<typename T> struct SetterOk<T,UINT_1,void (T::*)(unsigned int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,CHAR_1,void (T::*)(char)> { static const bool value = true; };
template<typename T> struct SetterOk<T,BOOL_1,void (T::*)(bool)> { static const bool value = true; };
template<typename T> struct SetterOk<T,INT_2,void (T::*)(int,int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,FLOAT_2,void (T::*)(float,float)> { static const bool value = true; };
template<typename T> struct SetterOk<T,UINT_2,void (T::*)(unsigned int, unsigned int)> { static const bool value = true; };
template<typename T> struct SetterOk<T,CHAR_2,void (T::*)(char,char)> { static const bool value = true; };
template<typename T> struct SetterOk<T,BOOL_2,void (T::*)(bool,bool)> { static const bool value = true; };
template <bool, class T = void> struct enable_if {};
template <class T> struct enable_if<true, T> { typedef T type; };
template<typename T, Type Et>
struct Helper
{
template<typename U>
static void func(U method, typename enable_if<SetterOk<T,Et,U>::value>::type* dummy = 0)
{
}
};
class test
{
public:
void init()
{
Helper<test,INT_2>::func(&test::set);
}
void set2(int);
void set(int);
void set(int,int);
void set(float,float);
};
int main()
{
test t;
t.init();
return 0;
}
I'm expecting it to choose the right function between all possible. The problem is that the compiler says "cannot deduce template argument as function argument is ambiguous".
It seems I don't know how to use enable_if, because if so the compiler would only allow the specialization if the specified function has the right type...
Note that I want to have C++03 solutions (if possible) - my code must compile on some old compilers.
Thanks in advance
You can never refer to an overloaded function without disambiguating it (means: static_casting it to the correct type). When you instantiate Helper::func the type of the function argument cannot be known without ever disambiguating it.
The reason it doesn't compile is quite simply that there are several different overloaded functions and it doesn't know which one you mean. Granted, only one of these (void set(int,int)) would actually compile, given the specialization Helper<test,INT_2>. However, this is not enough for the compiler to go on.
One way of getting this to compile would be to explicitly cast &test::set to the appropriate type:
Helper<test,INT_2>::func(static_cast<void (test::*)(int,int)>(&test::set));
Another way would be to use explicit template specialization:
Helper<test,INT_2>::func<void (test::*)(int,int)>((&test::set));
Either way, you need to let the compiler know which of the set functions you are trying to refer to.
EDIT:
As I understand it, you want to be able to deduce, from the use of a Type, which function type should be used. The following alternative achieves this:
template<typename T, Type Et> struct SetterOK{};
template<typename T> struct SetterOK<T,INT_1> {typedef void (T::*setter_type)(int);};
template<typename T> struct SetterOK<T,FLOAT_1> {typedef void (T::*setter_type) (float);};
// ...
template<typename T> struct SetterOK<T,INT_2> {typedef void (T::*setter_type)(int,int);};
// ....
template<typename T, Type Et>
struct Helper
{
template<typename U>
static void func(U method)
{
}
};
class test
{
public:
void init()
{
Helper<test,INT_2>::func<SetterOK<test,INT_2>::setter_type >(&test::set);
}
void set2(int);
void set(int);
void set(int,int);
void set(float,float);
};
int main()
{
test t;
t.init();
return 0;
}
ADDITIONAL EDIT:
A thought just occurred to me. In this special case which you've done, where U is SetterOK::setter_type, things can be simplified further by completely removing the template arguments for func:
static void func(typename SetterOK<T,Et>::setter_type method)
{
}
This would make the init method a simpler:
void init()
{
Helper<test,INT_2>::func(&test::set);
}

How to write SFINAE to test for parser rule?

I have a sfinae class that tests whether a class is a parser rule (AXE parser generator library).
The axe::is_rule<P>::value should evaluate to true iff P satisfies parser rule requirements. A parser rule must have one of the following member functions, taking a pair of iterators and returning axe::result<Iterator>:
template<class Iterator>
axe::result<Iterator> P::operator()(Iterator, Iterator);
, or its specialization, or non-template for some type CharT
axe::result<CharT*> P::operator()(CharT*, CharT*);
, or const versions of the above. Theoretically, there can be more than one overloaded operator(), though in practice a test for a single operator() with one of the above signatures would suffice.
Unfortunately, current implementation of is_rule takes care of only some, but not all cases. There are some unfortunate classes, that fail the is_rule test:
#define AXE_ASSERT_RULE(T)\
static_assert(axe::is_rule<typename std::remove_reference<T>::type>::value, \
"type '" #T "' is not a rule");
For example, the following unfortunate types fail the test:
struct unfortunate
{
axe::result<const unsigned char*>
operator()(const unsigned char*, const unsigned char*);
};
AXE_ASSERT_RULE(unfortunate);
// or same using lambda
auto unfortunate1 = [](const unsigned char*, const unsigned char*)
->axe::result<const unsigned char*> {};
AXE_ASSERT_RULE(decltype(unfortunate1));
typedef std::vector<char>::iterator vc_it;
struct unfortunate2 { axe::result<vc_it> operator()(vc_it, vc_it) const; };
AXE_ASSERT_RULE(unfortunate2);
typedef axe::result<const char*> (unfortunate3)(const char*, const char*);
AXE_ASSERT_RULE(unfortunate3);
struct rule { template<class I> axe::result<I> operator()(I, I); };
class unfortunate4 : public rule {};
AXE_ASSERT_RULE(unfortunate4);
Current solution in AXE is to wrap those in a forwarding wrapper (class r_ref_t), which, of course, creates syntactic warts (after all, parser generator is all about syntactic sugar).
How would you modify the sfinae test in is_rule to cover the unfortunate cases above?
I think the API of is_rule is not sufficient. For example unfortunate is a rule only if used with iterators of type const unsigned char*. If you use unfortunate with const char*, then it doesn't work, and is thus not a rule, right?
That being said, if you change the API to:
template <class R, class It> struct is_rule;
then I think this is doable in C++11. Below is a prototype:
#include <type_traits>
namespace axe
{
template <class It>
struct result
{
};
}
namespace detail
{
struct nat
{
nat() = delete;
nat(const nat&) = delete;
nat& operator=(const nat&) = delete;
~nat() = delete;
};
struct any
{
any(...);
nat operator()(any, any) const;
};
template <class T>
struct wrap
: public any,
public T
{
};
template <bool, class R, class It>
struct is_rule
{
typedef typename std::conditional<std::is_const<R>::value,
const wrap<R>,
wrap<R>>::type W;
typedef decltype(
std::declval<W>()(std::declval<It>(), std::declval<It>())
) type;
static const bool value = std::is_convertible<type, axe::result<It>>::value;
};
template <class R, class It>
struct is_rule<false, R, It>
{
static const bool value = false;
};
} // detail
template <class R, class It>
struct is_rule
: public std::integral_constant<bool,
detail::is_rule<std::is_class<R>::value, R, It>::value>
{
};
struct unfortunate
{
axe::result<const unsigned char*>
operator()(const unsigned char*, const unsigned char*);
};
#include <iostream>
int main()
{
std::cout << is_rule<unfortunate, const unsigned char*>::value << '\n';
std::cout << is_rule<unfortunate, const char*>::value << '\n';
}
For me this prints out:
1
0
I made the rule slightly more lax than you specified: The return type only has to be implicitly convertible to axe::result<It>. If you really want it to be exactly axe::result<It> then just sub in std::is_same where I used std::is_convertible.
I also made is_rule derive from std::integral_constant. This can be very convenient for tag dispatching. E.g.:
template <class T>
void imp(T, std::false_type);
template <class T>
void imp(T, std::true_type);
template <class T>
void foo(T t) {imp(t, is_rule<T, const char*>());}