Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I avoid writing `::value` and `::type` when using `std::enable_if`? [cppx]

Note: This is a question-with-answer in order to document a technique that others might find useful, and in order to perhaps become aware of others’ even better solutions. Do feel free to add critique or questions as comments. Also do feel free to add additional answers. :)


In some of my code, namely the header rfc/cppx/text/String.h, I found the following mysterious snippet:
template< class S, class enable = CPPX_IF_( Is_a_< String, S > ) >
void operator!  ( S const& )
{ string_detail::Meaningless::operation(); }

The operator! is in support of a String class that has an implicit conversion to raw pointer. So I overload (among others) operator! for this class and derived classes, so that inadvertent use of a non-supported operator will give a suitable compilation error, mentioning that it's meaningless and inaccessible. Which I think is much preferable to such usage being silently accepted with an unexpected, incorrect result.

The CPPX_IF_ macro supports Visual C++ 12.0 (2013) and earlier, which finds C++11 using to be mostly beyond its ken. For a more standard-conforming compiler I would have written just …

template< class S, class enable = If_< Is_a_< String, S > > >
void operator!  ( S const& )
{ string_detail::Meaningless::operation(); }

This looks like std::enable_if,

template< class S, class enabled = typename std::enable_if< Is_a_< String, S >::value, void >::type >
void operator!  ( S const& )
{ string_detail::Meaningless::operation(); }

except that the If_ or CPPX_IF_, and its expression, is much more concise and readable.

How on Earth did I do that?

like image 610
Cheers and hth. - Alf Avatar asked Jan 06 '14 07:01

Cheers and hth. - Alf


3 Answers

In C++14, variable templates make type traits a lot more comfortable to look at. Combine that with C++11 template aliases, and all the cruft disappears:

template <typename A, typename B>
bool is_base_of_v = std::is_base_of<A, B>::value;

template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

Usage:

template <typename B, typename D>
enable_if_t<is_base_of_v<B, D>, Foo> some_function(B & b, D & d) { /* ... */ }

"Type" aliases of the form _t are in fact planned as part of the standard library for C++14, see [meta.type.synop].

like image 140
Kerrek SB Avatar answered Oct 12 '22 07:10

Kerrek SB


The facility for a comfortable-with-C++11-using compiler is simply …

namespace cppx {
    using std::enable_if;

    template< class Condition_type, class Result_type = void >
    using If_ = typename enable_if<Condition_type::value, Result_type>::type;

}  // namespace cppx

Support for a more using-challenged compiler such as Visual C++ 12.0 and earlier (it understands the basic use of using but gets more and more unreliable the more the usage context has things like enable_if) is a bit more involved, building on a C++03-style solution like …

namespace cppx {
    using std::enable_if;

    template<
        class Condition_type,
        class Result_type = void,
        class enabled = typename enable_if<Condition_type::value, void>::type 
        >
    struct If_T_
    {
        typedef Result_type     T;
        typedef Result_type     type;
    };

}  // namespace cppx

This basically only provides a more readable name and dispenses with the ::value in a condition. In order to also dispense with typename and ::type I use a macro. But since the expression will generally be a template expression there may be commas that the preprocessor will interpret as argument separators, so that the preprocessor may see multiple arguments.

The solution I use for that (the time of C++03 is over for me) is to use a C99/C++11 variadic macro, …

#define CPPX_IF_( ... ) \
    typename cppx::If_T_< __VA_ARGS__ >::T

A corresponding macro could be defined for use of this functionality without the typename.


Complete listing, file rfc/cppx/utility/If_.h:

#pragma once
// Copyright (c) 2013 Alf P. Steinbach

#include <type_traits>      // std::enable_if

#define CPPX_IF_( ... ) \
    typename cppx::If_T_< __VA_ARGS__ >::T

namespace cppx {
    using std::enable_if;

    template< class Condition_type, class Result_type = void >
    using If_ = typename enable_if<Condition_type::value, Result_type>::type;

    template<
        class Condition_type,
        class Result_type = void,
        class enabled = typename enable_if<Condition_type::value, void>::type 
        >
    struct If_T_
    {
        typedef Result_type     T;
        typedef Result_type     type;
    };

}  // namespace cppx

Also, for completeness, Is_a_ is defined simply as …

template< class Base, class Derived_or_eq >
using Is_a_ = std::is_base_of<Base, Derived_or_eq>;

which is a use of using that Visual C++ 12.0 does understand.


To be able to use compound conditions without writing ::value everywhere, the following definitions come in handy. Essentially these are boolean operators that operate on types. It is perhaps worth noting especially the general exclusive OR operator, which is not implemented in terms of binary XOR (e.g. !=): that would have yielded an operator that checked for the odd number of true values, which is of little practical utility except for the special case of exactly two arguments.

namespace cppx {
    using std::integral_constant;

    template< bool c >
    using Bool_ = integral_constant<bool, c>;

    using False = Bool_<false>;     // std::false_type;
    using True  = Bool_<true>;      // std::true_type;

    template< bool v, class First, class... Rest >
    struct Count_
    {
        enum{ value = Count_<v, First>::value + Count_<v, Rest...>::value };
    };

    template< bool v, class X >
    struct Count_<v, X>
    {
        enum{ value = int(!!X::value == v) };
    };

    template< class X >
    using Not_ = Bool_<Count_<true, X>::value == 0>;                   // NOT

    template< class... Args >
    using All_ = Bool_<Count_<false, Args...>::value == 0>;            // AND

    template< class... Args >
    using Some_ = Bool_<Count_<true, Args...>::value != 0>;     // General inclusive OR.

    template< class... Args >
    using Either_ = Bool_<Count_<true, Args...>::value == 1>;   // General exclusive OR.
}  // namespace cppx

Disclaimer: none of the code has been extensively tested, and C++ compiler quirks in the area of template meta-programming are common.

like image 44
Cheers and hth. - Alf Avatar answered Oct 12 '22 07:10

Cheers and hth. - Alf


We have C++03 and C++11 and C++14 solutions, but Concepts Lite is missing:

template <typename Derived, typename Base>
constexpr bool Is_a_() {
  return std::is_base_of<Base, Derived>::value;
}

template<Is_a_<String> S>
void operator! ( S const& )
{ string_detail::Meaningless::operation(); }

or the even more terse:

template <typename Derived, typename Base>
concept bool Is_a_() {
  return std::is_base_of<Base, Derived>::value;
}

void operator! ( Is_a_<String> const& )
{ string_detail::Meaningless::operation(); }

I highly recommend skimming through the tutorial (Section 2) of the Concepts Lite paper to get a sense of just how much better the world will be after we are freed from our enable_if overlords.

like image 6
Casey Avatar answered Oct 12 '22 06:10

Casey