问题
I want to specialize a function for a subset of classes which: have a certain static data member variable, and such variable has only certain possible values.
The code below illustrates the intent, but it does not compile unless I comment out the lines related to the B
classes in main
. This is because code
is not a member of the Bx
classes, but the enable_if
condition is valid if the template argument has a code
member variables. How should I modify it?
Unfortunately I work with very old compilers, hence no support for C++11: I compile with the option -std=c++03
.
Thanks
#include <iostream>
#include <boost/core/enable_if.hpp>
#include <boost/mpl/or.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/bool.hpp>
using std::cout;
using namespace boost;
using namespace boost::mpl;
template <int N> struct A1 { static const int code = N; };
template <int N> struct A2 { static const int code = N; };
// ... other classes with static data member 'code'
template <int N> struct AN { static const int code = N; };
struct B1{};
struct B2{};
// ... other classes potentially passd as argument to the foo function
struct BN{};
template <typename T>
struct Condition : or_<bool_<T::code == 1>, bool_<T::code == 2> > {};
template <typename T>
typename enable_if<not_<Condition<T> >, void>::type
foo(const T& arg) { cout << "This class does not have a static member code or its value is not 1 or 2\n"; }
template <typename T>
typename enable_if<Condition<T>, void>::type
foo(const T& arg) { cout << "This class has a static member code and its value is " << T::code << "\n"; }
int main()
{
foo(A1<0>()); // this should match the 1st version of foo
foo(A2<1>()); // this should match the 2nd version of foo
foo(AN<2>()); // this should match the 2nd version of foo
foo(B1()); // this should match the 1st version of foo
foo(BN()); // this should match the 1st version of foo
}
回答1:
The real problem is that you need a C++03 solution, so you can use SFINAE but not all language improvement that are available starting from C++11.
Anyway, I propose you a solution that is convoluted like yours (maybe more) but is completely boost free.
If you define a trivial bool wrapper (that can roughly substitute the C++11 std::true_type
and std::false_type
)
template <bool B>
struct bool_wrapper
{ static const bool value = B; };
you can define your condition as follows
template <typename, typename = bool_wrapper<true> >
struct cond : public bool_wrapper<false>
{ };
template <typename T>
struct cond<T, bool_wrapper<(1 == T::code) || (2 == T::code)> >
: public bool_wrapper<true>
{ };
and if you define a enable_if
type traits (the same as the C++11 std::enable_if
)
template <bool, typename = void>
struct enable_if
{ };
template <typename T>
struct enable_if<true, T>
{ typedef T type; };
you can SFINAE enable/disable your foo()
functions
template <typename T>
typename enable_if<false == cond<T>::value>::type foo (T const & arg)
{ std::cout << "no static member code or value not 1 and not 2\n"; }
template <typename T>
typename enable_if<true == cond<T>::value>::type foo (T const & arg)
{ std::cout << "static member code and its value is " << T::code << "\n"; }
The following is a full working C++98 example
#include <iostream>
template <int N> struct A1 { static const int code = N; };
template <int N> struct A2 { static const int code = N; };
// ...
template <int N> struct AN { static const int code = N; };
struct B1{};
struct B2{};
// ...
struct BN{};
template <bool B>
struct bool_wrapper
{ static const bool value = B; };
template <typename, typename = bool_wrapper<true> >
struct cond : public bool_wrapper<false>
{ };
template <typename T>
struct cond<T, bool_wrapper<(1 == T::code) || (2 == T::code)> >
: public bool_wrapper<true>
{ };
template <bool, typename = void>
struct enable_if
{ };
template <typename T>
struct enable_if<true, T>
{ typedef T type; };
template <typename T>
typename enable_if<false == cond<T>::value>::type foo (T const & arg)
{ std::cout << "no static member code or value not 1 and not 2\n"; }
template <typename T>
typename enable_if<true == cond<T>::value>::type foo (T const & arg)
{ std::cout << "static member code and its value is " << T::code << "\n"; }
int main ()
{
foo(A1<0>()); // match the 1st version of foo
foo(A2<1>()); // match the 2nd version of foo
foo(AN<2>()); // match the 2nd version of foo
foo(B1()); // match the 1st version of foo
foo(BN()); // match the 1st version of foo
}
I don't know the boost classes your using but I suppose you can modify your code (to works almost as my no-boost solution) as follows
template <typename, typename = bool_<true> >
struct Condition : public bool_<false>
{ };
template <typename T>
struct Condition<T, bool_<(1 == T::code) || (2 == T::code)> >
: public bool_<true>
{ };
-- EDIT --
The OP ask
I do not understand how it works for the case A1<0>. The specialization of Condition should be the preferred match, with the 2nd argument expanding to bool_. That class inherits from bool_, so, it should pick the wrong versions of foo. However it works. How is it possible?
Well... when you write foo(A1<0>())
, the compiler must understand if cond<A1<0>>::value
is true
or false
to enable the first version of foo()
or the second one.
So the compiler must implement cond<A1<0>>
. But there isn't a cond
template class that receive only a typename. Anyway, the compiler find that
template <typename, typename = bool_wrapper<true> >
struct cond;
matches using the default value for the second template parameter.
There aren't alternative, so there isn't ambiguity, so cond< A<1> >
become cond< A<1>, bool_wrapper<true> >
Now the compiler must choose between the main version of cond<typename, typename>
(the one that inherit from bool_wrapper<false>
) and the specialization (the one that inherit from bool_wrapper<true>
).
cond< A<1>, bool_wrapper<true> >
surely matches the main version, but matches also the specialization? If matches the also the specialization, the compiler must prefer the specialization.
So we need to see if cond< A<0>, bool_wrapper<true> >
matches the specialization.
Using A<0>
as T
, we have that the specialization become
cond< A<0>, bool_wrapper<(1 == A<0>::code) || (2 == A<0>::code)> >
that is
cond< A<0>, bool_wrapper<(1 == 0) || (2 == 0)> >
that is
cond< A<0>, bool_wrapper<false || false> >
that is
cond< A<0>, bool_wrapper<false> >
and this doesn't matches cond< A<0>, bool_wrapper<true> >
.
So cond< A<0> >
, that is cond< A<0>, bool_wrapper<true> >
, matches only the main version of cond<typename, typename>
, so inherit from bool_wrapper<false>
.
Now we can give a look at cond< A<1> >
.
As for cond< A<0> >
, the only template cond
that matches cond< A<1> >
is cond<typename, typename>
with the second typename
with the default value.
So cond< A<1> >
is cond< A<1>, bool_wrapper<true> >
.
But cond< A<1>, bool_wrapper<true> >
matches only the main version of cond<typename, typename>
or also the specialization?
We can see that using A<1>
as T
, we have that the specialization become
cond< A<1>, bool_wrapper<(1 == A<1>::code) || (2 == A<1>::code)> >
that is
cond< A<1>, bool_wrapper<(1 == 1) || (2 == 1)> >
that is
cond< A<1>, bool_wrapper<true || false> >
that is
cond< A<1>, bool_wrapper<true> >
and this matches cond< A<1>, bool_wrapper<true> >
.
So, for cond< A<1> >
, AKA cond< A<1>, bool_wrapper<true>
, both versions of cond<typename, typename>
match, so the compiler must choose the specialization, so cond< A<1> >
inherit from bool_wrapper<true>
.
回答2:
Based on J. Zwinck's suggestion, I got it to work.
Putting the two conditions in an and_
template class does not work for the same reason as stated above. However, I can put the first condition (class has member variable code
) in an enable_if
, then the second condition (code has specific values) inside the class (see example below).
If is a bit convoluted though. If anybody can suggest a more elegant solution I will accept it.
#include <boost/tti/has_static_member_data.hpp>
BOOST_TTI_HAS_STATIC_MEMBER_DATA(code)
template <typename T, typename Enable = void>
struct Condition : false_type {};;
template <typename T>
struct Condition<T, typename enable_if<bool_<has_static_member_data_code<T,const int>::value> >::type>
{
typedef or_<bool_<T::code == 1>, bool_<T::code == 2> > type;
const static bool value = type::value;
};
来源:https://stackoverflow.com/questions/52565407/use-of-enable-if-to-match-only-classes-which-have-a-certain-static-data-member