16

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?

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331

3 Answers3

28

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].

Herb Sutter
  • 1,888
  • 1
  • 14
  • 14
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Nice to know, thanks! But, the one fly in the ointment, can't practically use that in Windows until Visual C++ supports it... – Cheers and hth. - Alf Jan 06 '14 at 09:42
  • @TemplateRex: Yes, C++111 template aliases are used in both mine and Kerrek's answers, but no, the VC 12.0 support is not complete, nor entirely bug-free. Regarding upcoming C++14 "variable templates", the code above does not compile with Visual C++ 12.0 (2013). Nor does it compile with g++ 4.8.2. So I think it is consistent with my view of SO (a site of roughly 50% Herb Schildt-like disinformation, perhaps the greatest such on the net) that your comment was upvoted by two readers. – Cheers and hth. - Alf Jan 06 '14 at 13:34
  • +1 (right when you posted this) for the C++14 information. however, it's just the shape of things to come. – Cheers and hth. - Alf Jan 06 '14 at 13:46
  • @TemplateRex: re "Can you give a small example of where this feature won't work on VC++", as mentioned twice **the above (this answer) is one example** where it does not work, is not supported, as of VIsual C++ 2013. But I can't predict the future, sorry. – Cheers and hth. - Alf Jan 23 '14 at 10:08
  • Danger Will Robinson! Substitution of an alias template isn't in the SFINAE context of a declaration. If your compiler doesn't complain when `::type` is absent, it probably should, and others will. `_t` does work nicely but not with SFINAE, at least as currently specified. – Potatoswatter Jan 24 '14 at 02:48
  • @Potatoswatter: Oh no - I never knew that. Thanks! So that basically makes this answer completely wrong? (What's the relevant change in C++14?) – Kerrek SB Jan 24 '14 at 09:19
  • @Potatoswatter: Weirdly enough, neither GCC 4.8 nor Clang 3.5 catch this, and even perform SFINAE correctly. Is this a defect report that may have been fixed in compilers already? – Kerrek SB Jan 24 '14 at 09:28
  • @Potatoswatter: And I'm pretty sure that Xeo and RMF have blogged about that technique years ago. Were they wrong? – Kerrek SB Jan 24 '14 at 13:17
  • @Potatoswatter: In fact, [this answer](http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae) seems to say the exact opposite, i.e. that the construction *is* fine. – Kerrek SB Jan 24 '14 at 21:39
  • @KerrekSB No, that answer says that it won't work. See the comments after the answer: Clang is the one that rejected the example. I'm not aware of any normative changes; the non-application of SFINAE is the result of an alias template context being the same as any other template context. The DR linked from the answer doesn't quite match, because the context used for access checking is based on enclosing classes, and SFINAE applicability is based on presence within the immediate declaration. I don't follow the blogs and don't want to judge wrongness; I'm just interpreting the standard. – Potatoswatter Jan 25 '14 at 06:46
  • 1
    Ah, I just checked and N3797 does specify a `std::enable_if_t`. I will submit a DR. – Potatoswatter Jan 25 '14 at 06:52
  • @Potatoswatter; thanksy! i'm have too low energy to fix things right now (some new surgery yesterday), and know from experience that postponed => soon forgotten. but thanks! summing up, the standard doesn't seem to in line with its intention; g++ supports the intention; msvc is unclear since it's so full of bugs in this area; clang supports the literal interpretation; the macro in my answer should work anyway; and Real Soon Now, with C++1x, everything will be OK. Possibly. Yes? ;-) – Cheers and hth. - Alf Jan 25 '14 at 10:49
  • @Cheersandhth.-Alf I'm not sure how the drafting process works, but either they will fix spec according to the DR in time for C++14, or `std::enable_if_t` will "just work" regardless of the core language spec. (A vendor would have to be braindead to ship a broken `enable_if_t`, but I guess I wouldn't put it past MSVC.) So, there's nothing to worry about, but defining your own `enable_if_t` isn't particularly good practice. A custom macro solution is just fine though. – Potatoswatter Jan 25 '14 at 13:27
  • Selected this as "solution", rather than my own, because compiler support for the relevant features needed in this answer is now common. – Cheers and hth. - Alf Feb 02 '16 at 23:07
  • C++20 will add the requires keyword to make the notation shorter and more readable – Matthias Nov 16 '17 at 11:08
9

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.

Chen Li
  • 4,824
  • 3
  • 28
  • 55
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 2
    New template aliases like `std::enable_if_t` are part of C++14 I believe. – Kerrek SB Jan 06 '14 at 08:03
  • @KerrekSB, Cool, I didn't know they actually got those implemented. Do you know if they did any `_v` ones for `::value` with variable templates or anything? – chris Jan 06 '14 at 08:06
  • @KerrekSB: thanks! i didn't know. **[it looks like](http://en.cppreference.com/w/cpp/types/enable_if#Helper_types)** it dispenses with the `typename` and `::type` verbiage, but not the `::value`. anyway, not supported by visual c++ yet. probably not for a long time... – Cheers and hth. - Alf Jan 06 '14 at 08:16
  • Dispensing with `::value` would require variable templates. Those are part of C++14, too, but I don't know if corresponding new traits are planned. Doesn't look like it, though that's a shame. – Kerrek SB Jan 06 '14 at 08:18
  • @KerrekSB: uh, well, the above appears to work nicely, and does dispense with the `::value`. not sure what you mean by "would require variable templates". not even sure what that is? – Cheers and hth. - Alf Jan 06 '14 at 08:20
  • 1
  • @Jarod42: `If_` shouldn't IMO have the responsibility of supporting boolean expression, just like normal `if` doesn't have that responsibility. Instead they accept a value (or type) resulting from such an *expression*. E.g., instead of the verbose `is_blah::value && is_duh::value` you can write just `Both_, is_duh>`, where `Both_`can be defined as `template< class A, class B > using Both_ = std::integral_constant`. – Cheers and hth. - Alf Jan 06 '14 at 09:53
  • @Jarod42: but you have a point that the `If_.h` header should better provide operations such as `Both_` and `Not_` and `Any_`. I'll update the answer. – Cheers and hth. - Alf Jan 06 '14 at 09:59
  • @Jarod42: I agree with the `All_`, that's what I already coded up. In addition I added `Some_` (logical OR), `Either_` (logical XOR) and `Not_`, as well as `True_count_` (support for variadic `Either_`). Now for testing and so on, all I had beforehand was the `Not_`... ;-) – Cheers and hth. - Alf Jan 06 '14 at 10:20
  • Ok, you may use `Bool_` (extra typing) to handle other cases as checking `N % 2 == 0` in case like `template`... – Jarod42 Jan 06 '14 at 11:39
  • @Jarod42: since this scheme gives much less typing than without it, as illustrated, I think your comment about "extra typing" is misleading. – Cheers and hth. - Alf Jan 06 '14 at 13:36
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/44623/discussion-between-jarod42-and-cheers-and-hth-alf) – Jarod42 Jan 06 '14 at 13:40
  • (Also posted to Kerreck) Danger Will Robinson! Substitution of an alias template isn't in the SFINAE context of a declaration. If your compiler doesn't complain when `::type` is absent, it probably should, and others will. `_t` does work nicely but not with SFINAE, at least as currently specified. – Potatoswatter Jan 24 '14 at 02:50
  • @Potatoswatter: that sounds ominous, but I don't follow. could you provide a clarifying example, please? – Cheers and hth. - Alf Jan 24 '14 at 04:40
  • @Cheersandhth.-Alf If a declaration passes a `false` argument to the alias template `If_`, then instead of SFINAE causing it to be ignored, a complying compiler will give a hard error. The rest of your answer, which mostly avoids C++11, looks OK. – Potatoswatter Jan 24 '14 at 06:09
6

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.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 2
    Concepts Lite also has the advantage of being shiny and new, so Microsoft will probably implement it in their compiler before they get around to fixing the C++98 bugs. – Casey Jan 06 '14 at 18:43