Recently I came across a blog post by Andrzej Krzemieński [Krzemieński] outlining how to apply an overload so that the behaviour of a function could depend on the type passed to it; if the type passed was convertible to the value type of the containing object the function could return the type passed, otherwise it could attempt to use a function object to return the value.
The blog post demonstrates two solutions for existing C++11 compilers and also presents a possible solution if/when ‘Concepts Lite’ [Sutton13] makes it into the standard; his post explains this in some detail so I won’t repeat here what he has already said.
When I read it, I wondered whether it might be possible to solve the problem the other way around, in other words if the passed type was a function object whose function call operator returned a suitable type, then the function should use that, otherwise it should assume the type was convertible and attempt to return the value (with a compile error if that didn’t work). Although the C++11 solutions that Andrzej presented would also work with C++98/03 and Boost type traits, I thought it might be possible to build a solution that used only features available in C++98/03 (some overloads with a little bit of type matching). It turns out that we can get quite close to a solution in C++98/03 with some constraints on the function call operator.
During the review of this article for Overload, Jonathan Wakely showed how easily the same thing could be done in C++11; it seems that this solution also works with several versions of Visual C++ (at least back as far as VS2010), so I’ll show his code at the end.
The problem
The underlying problem identified is fairly simple; allow a function to return a ‘default value’ if some condition is met; in the example in Listing 1 if the ‘optional’ class hasn’t had an initialised value then return the default (this example is taken from Andrzej’s original blog, slightly simplified).
#define REQUIRE(x) std::cout << #x << std::endl; assert(x); template <typename T> class optional { // ... template <typename U> T value_or(U const& v) const { // condition changed "if(*this)" in Andrzej's // example since people found that confusing... if (m_initialized) // get contained value return this->value(); else // get v converted to T return v; } }; ... optional<int> v1(20); // value was initialised, return it... REQUIRE( v1.value_or(42) == 20 ); // value was not initialised, return default... optional<int> v2; REQUIRE( v2.value_or(42) == 42 ); |
Listing 1 |
Andrzej then extends the problem such that the value_or()
function can also accept a type that could be ‘callable’, so it might be either a function pointer or a functor [Wikipedia]. He presents two solutions for C++11; one using std::is_convertible
to provide tag dispatching [Boost-a] or std::enable_if
[Boost-b] to enable/remove functions from the overload set. It is this additional requirement that I want to look at...
C++98/03 overloads – calling with a function pointer
To call with a function pointer we can simply provide a function overload that matches a function pointer. This will match any free/static function that takes no parameters (so will give a compile error if the return type is not convertible to the value type of the containing object). Note that a class with a conversion operator also ‘just works’... (see Listing 2).
// function to be called... int function() { return -1; } // struct with conversion operator doesn't // compile... struct conversion { operator double() const { return 13.0; } }; template <typename T> class optional { // ... // overload for free/static functions template <typename U> T value_or(U (*fn)()) const { if (m_initialized) // get contained value return this->value(); else // call function.. return fn(); } // default for all other types, as before... template <typename U> T value_or(U const& v) const { if (m_initialized) return this->value(); else return v; } }; ... optional<double> v2; // as before... REQUIRE( v2.value_or(42) == 42 ); // calls passed function... REQUIRE( v2.value_or(&function) == -1 ); conversion conv; // fine, conversion just works... REQUIRE( v2.value_or(conv) == 13.0 ); |
Listing 2 |
Calling with a function object
To allow calling with a function object, we can provide an additional function with two overloads that will either use a function object or return the value. This is a variation of a commonly used idiom used by tag dispatching but in our case instead of creating a ‘tag’ we will allow the compiler to match a function overload if the supplied object has a function call operator (i.e. operator()
).
To see how this will work, consider the more general case of a function that accepts the supplied value as the first argument and ‘anything’ as the second (see Listing 3).
template <typename T> class optional { // ...other functions as before... // ellipsis matches everything... template <typename U> static T functor_or_default(const U& v, ...) { return v; } // default for all other types template <typename U> T value_or(U const& v) const { if (m_initialized) return this->value(); else // get v converted to T or functor... return functor_or_default<U>(v, 0); } }; ... optional<double> v2; // still works but now calls // 'functor_or_default'... REQUIRE( v2.value_or(42) == 42 ); |
Listing 3 |
This works because the ellipsis (...
) matches anything and consequently the function functor_or_default
will match the supplied arguments, (v
and 0
).
Fortunately, the compiler considers ellipsis to be the worst possible match so now all we need to do is provide an overload that is a better match than ellipsis if the type is a function object with a matching function call operator. Although our goal is to match the function call operator but we will start with something a little simpler to show how this works. For example, Listing 4 shows the code if we wanted to match int
.
template <typename T> class optional { public: // ... // overload called for 'int'... template <typename U> static T functor_or_default(const U& v, int) { return v; } // ellipsis still matches everything else... template <typename U> static T functor_or_default(const U& v, ...) { return v; } // default for all other types template <typename U> T value_or(U const& v) const { if (m_initialized) return this->value(); else // pass 'int' with value 0 return functor_or_default<U>(v, 0); } // ... }; ... optional<double> v2; // as before... REQUIRE( v2.value_or(42) == 42 ); |
Listing 4 |
In this case the first overload will be called since we passed zero (an int
with value == 0
) as the second argument to functor_or_default
.
Now all we need to do is replace the overload taking an int
with one that matches the function call operator. We’ll go in two steps; first, let’s imagine that the passed object has a function called default_value
(this makes the syntax slightly more readable...). Ideally, we would like to provide a version of functor_or_default
that matched a pointer to the member function, so we could replace functor_or_default
with something like Listing 5.
// works... struct functor { double default_value() const { return 3.142; } }; // conversion struct as before... template <typename T> class optional { public: // ... // replace call with 'int' with function call ptr template <typename U> static T functor_or_default(const U& v, T (U::*)() const) { return v.default_value(); } // as before... template <typename U> static T functor_or_default(const U& v, ...) { return v; } // default for all other types template <typename U> T value_or(U const& v) const { if (m_initialized) return this->value(); else return functor_or_default<U>(v, 0); } // ... }; ... optional<double> v2; functor fn; // fine, fn has 'default_value()' REQUIRE( v2.value_or(fn) == 3.142 ); // fine, calls ellipsis overload as before REQUIRE( v2.value_or(42) == 42 ); conversion conv; // doesn't compile...'conversion' doesn't have // 'default_value' function REQUIRE( v2.value_or(conv) == 13.0 ); |
Listing 5 |
Whilst this overload matches any class type and works for our functor, the body of the function now fails to compile for a class without a default_value
function so our class with a conversion operator no longer compiles; what we need is something more specific for the compiler to match so we provide a helper that gives another level of indirection (Listing 6).
... template <typename U, T (U::*)() const> struct has_functor {}; // now a specific match... template <typename U> static T functor_or_default(const U& v, has_functor<U, &U::default_value>*) { return v.default_value(); } ... conversion conv; // now fine, has_functor can't match // 'default_value' for conversion type // so overload removed from candidate functions... // ...calls function with ellipsis and operator // double() REQUIRE( v2.value_or(conv) == 13.0 ); |
Listing 6 |
The has_functor
helper allows us to declare a type that matches only if it has a matching function pointer; if the member function does not exist then the template overload is invalid and the compiler removes it from the candidate list of functions; this is known as ‘substitution failure is not an error’ [SFINAE]. This is a standard trick often used in tag dispatching, where it is usually passed to sizeof()
but here we’re using it directly as a parameter to the overload.
Now that we have this overload, we just need to fix up the syntax for calling a function call operator instead of a function named default_value
...so the final version looks like Listing 7.
// as before... struct functor { double operator ()() const { return 3.142; } }; struct conversion { operator double() const { return 13.0; } }; int function() { return -1; } template <typename T> class optional { public: optional() : m_initialized(false) {} explicit optional(const T& v) : m_initialized(true) , t(v) {} template <typename U, T (U::*)() const> struct has_functor {}; template <typename U> static T functor_or_default(const U& v, has_functor<U, &U::operator()>*) { return v(); } template <typename U> static T functor_or_default(const U& v, ...) { return v; } // overload for free/static functions template <typename U> T value_or(U (*fn)()) const { if (m_initialized) return this->value(); else return fn(); } // default for all other types template <typename U> T value_or(U const& v) const { if (m_initialized) return this->value(); else return functor_or_default<U>(v, 0); } T value() const { return t; } private: bool m_initialized; T t; }; ... optional<double> v2; // calls passed function... REQUIRE( v2.value_or(&function) == -1 ); functor fn; // fine, fn has function call operator REQUIRE( v2.value_or(fn) == 3.142 ); // fine, calls ellipsis overload REQUIRE( v2.value_or(42) == 42 ); conversion conv; // fine, calls ellipsis overload and // operator double() REQUIRE( v2.value_or(conv) == 13.0 ); |
Listing 7 |
Fixing the problems using C++11
If we pass a pointer to a free/static function then the compiler will attempt to convert the return type for us. Unfortunately the type matching helper for the function object requires an exact match for the function call operator – both the return type and any const/volatile qualifiers must be the same or the pointer won’t match and the ‘anything’ overload gets selected instead.
We would like to be able to match a function call operator that returns something convertible to the value type of ‘optional’ type instead of having a match for a specific function call operator and in C++11 we can do that with decltype
(Listing 8).
template <typename T> class optional { ... template <typename U> static auto functor_or_default (const U& v, int) -> decltype(static_cast<T>(v())) { return v(); } ... }; |
Listing 8 |
If I’ve understood this correctly, if type U
has a function call operator with a return type that is convertible (via static_cast<>()
) to type T
then the return type of this function overload of functor_or_default
will be valid; the function will be part of the overload set and the second parameter (int
) will be a better match than ellipsis (...
). Note that we can also remove the overload that takes a pointer to a free/static function as this overload handles both cases.
It turns out that this also works with many versions of Visual C++ even though their C++11 support is limited. So the final solution for C++11 (or VS2010 or later) looks like Listing 9.
template <typename T> class optional { public: optional() : m_initialized(false) {} explicit optional(const T& v) : m_initialized(true) , t(v) {} template <typename U> static auto functor_or_default(const U& v, int) -> decltype(static_cast<T>(v())) { return v(); } template <typename U> static T functor_or_default(const U& v, ...) { return v; } // default for all other types template <typename U> T value_or(U const& v) const { if (m_initialized) return this->value(); else return functor_or_default<U>(v, 0); } T value() const { return t; } private: bool m_initialized; T t; }; |
Listing 9 |
Jonathan also pointed out that although it is rarely useful, it can also successfully match things that aren’t quite function objects (for example, see Listing 10).
struct not_quite_functor { using func = int(*)(); operator func() const { return [] { return 1; }; } }; |
Listing 10 |
Wrap up
The solution presented by Andrzej answers the question ‘How can we select an overload for a type that is convertible?’ In this article I’ve tackled the problem from the opposite direction, i.e. ‘How can we select an overload that matches something passed that looks like a function?’
We’ve seen that this can be done in C++98/03 with no additional requirements from the standard library (or boost) but we do need to be very specific about exactly what function call operator will match. The C++11 version is very neat (thanks Jonathan!) and has the added bonus of working with many versions of Visual C++.
I know many organisations are still limited to using C++98/03 and I hope that this article has shown that alternative techniques are often possible even for older compilers.
References
[Boost-a] Generic Programming Techniques: http://www.boost.org/community/generic_programming.html#tag_dispatching
[Boost-b] Boost.EnableIf (Jaakko Järvi, Jeremiah Willcock, Andrew Lumsdaine, Matt Calabrese): http://www.boost.org/doc/libs/1_55_0/libs/utility/enable_if.html
[Krzemieński] Clever Overloading: Andrzej’s C++ blog http://akrzemi1.wordpress.com/2014/06/26/clever-overloading
[SFINAE] Substitution Failure Is Not An Error (SFINAE): http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/SFINAE
[Sutton13] ‘Concepts Lite: Constraining Templates with Predicates’ Andrew Sutton, Bjarne Stroustrup, Gabriel Dos Reis at: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3580.pdf
[Wikipedia] Function object: http://en.wikipedia.org/wiki/Function_object
Overload Journal #123 - October 2014 + Programming Topics
Browse in : |
All
> Journals
> Overload
> o123
(8)
All > Topics > Programming (877) Any of these categories - All of these categories |