Alternative Overloads

Alternative Overloads

By Malcolm Noyes

Overload, 22(123):8-11, October 2014


How do you return a default value given a condition? Malcolm Noyes presents solutions using older and newer C++ techniques.

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






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.