Selectively executing code without constexpr if, before modern C++

Viewed 87

I want to write some threading wrapper and I need to work with thread parameters as void * , since I am using a non-C++11 thread library. I stumbled upon a problem and prepared a minimal working example. Here is the code:

#include <iostream>

namespace chops {
    template <typename T, typename U>
    struct is_same {
        static const bool value = false;
    };

    template<typename T>
    struct is_same<T, T> {
        static const bool value = true;
    };
}

template <typename Functor, typename T>
typename Functor::return_type fun_wrapper(T const& arg) {
    Functor f;
    typename Functor::return_type ret = f(arg);
    return ret;
}

struct hello_world {
    void operator()(int count) {
        while(count--)
            std::cout << "hello, world!\n";
    }
    typedef void return_type;
};

struct is_even {
    bool operator()(int x) {
        if(!(x % 2))
            return true;
        else
            return false;
    }
    typedef bool return_type;
};

int main() {
    //fun_wrapper<hello_world>(3);
    fun_wrapper<is_even>(3);
} 

Here, if you want to execute the commented out line in the main, it won't compile since it wants to instantiate a template containing a line like

void x = //something

So I wrote myself an is_same type trait and want to execute that code only if Functor::return_type is not void. I want to have the effect:

if constexpr(!is_same<Functor::return_type, void>::value) {
    typename Functor::return_type res = f(arg);
    return ret;
} else {
    f(arg);
}

Since I can't use modern C++, yet alone C++17, I can't seem to find a way. I don't know how to achieve similar effects with things like SFINAE.

PS: Note that this is a contrived example and can be made to work with some modifications but in my actual program, I need to create an object of Functor::return_type if it is not void. So please do not eliminate that line in your answers. So, no just using return f(arg).

4 Answers

The simplest way is to create two overloads, one for when Functor::return_type is void and one for when it isn't. All you have to do is reimplement std::enable_if (e.g. in namespace chops):

template <bool expr, typename T = void>
struct enable_if
{
};

template <typename T>
struct enable_if<true, T>
{
    typedef T type;
};

and then replace the single fun_wrapper function template by two constrained ones:

template <typename Functor, typename T>
typename chops::enable_if<
    !chops::is_same<typename Functor::return_type, void>::value,
    typename Functor::return_type>::type 
fun_wrapper(T const& arg) {
    Functor f;
    typename Functor::return_type ret = f(arg);
    return ret;
}

template <typename Functor, typename T>
typename chops::enable_if<chops::is_same<typename Functor::return_type, void>::value>::type 
fun_wrapper(T const& arg) {
    Functor f;
    f(arg);
}

Live demo on wandbox.

You can do this by partially specializing a class template over the return type of your functor (R is the return type, and only if that is void the compiler chooses the specialized version):

template <typename R, typename Functor, typename T>
struct fun_wrapper_impl {
    static R wrap(T const& arg) {
        Functor f;
        R ret = f(arg);
        return ret;
    }
};

template <typename Functor, typename T>
struct fun_wrapper_impl<void, Functor, T> {
    static void wrap(T const& arg) {
        Functor f;
        f(arg);
    }
};

template <typename Functor, typename T>
typename Functor::return_type fun_wrapper(T const& arg) {
    return fun_wrapper_impl<typename Functor::return_type, Functor, T>::wrap(arg);
}

Godbolt with C++98

This allows you to specialize behavior for any specific type. If you would instead prefer to use is_same (maybe because in a less contrived example you want to introduce additional conditions mapping to the same implementation later), you can also use tag dispatching:

template <bool V>
struct tag {};

template <typename Functor, typename T>
typename Functor::return_type fun_wrapper_impl(T const& arg, tag<false>) {
    Functor f;
    typename Functor::return_type ret = f(arg);
    return ret;
}

template <typename Functor, typename T>
void fun_wrapper_impl(T const& arg, tag<true>) {
    Functor f;
    /* returns void */ f(arg);
}

template <typename Functor, typename T>
typename Functor::return_type fun_wrapper(T const& arg) {
    tag<chops::is_same<typename Functor::return_type, void>::value> the_tag;
    return fun_wrapper_impl<Functor, T>(arg, the_tag);
}

Godbolt with C++98

A rather simple solution based on two tricks which were used before "modern C++":

  1. template specialization

  2. Using a replacement for void if nothing else works.

Sample:

#include <iostream>

template <typename VALUE>
struct Property {
  VALUE value;

  void set(VALUE value) { this->value = value; }
  const VALUE& get() const { return value; }
};

struct Void { };

template <>
struct Property<void>
{
  void set() { }
  Void get() { return Void(); }
};

std::ostream& operator<<(std::ostream &out, const Void &value)
{
  return out;
}

int main()
{
  Property<int> propInt;
  propInt.set(123);
  std::cout << propInt.get() << '\n';
  Property<void> propVoid;
  std::cout << propVoid.get() << '\n';
  return 0;
}

Output:

123

Live Demo on coliru

Please, note, that I compiled with -std=c++03 to grant as best as possible that it will work on non-"modern" C++ as well.

If you don't really need to store the functor return value but only return it yourself, this works in C++03:

#include <iostream>

template <class Functor, class T>
typename Functor::return_type fun_wrapper(T const& arg) {
    return Functor()(arg);
}

struct hello_world {
    void operator()(int count) {
        while(count--)
            std::cout << "hello, world!\n";
    }
    typedef void return_type;
};

int main() {
    fun_wrapper<hello_world>(3);
} 

If this is a solution which only works for your MCVE, an explicit specialization for void is required.

Related