Tamás Szelei
Tamás Szelei

Reputation: 23921

Specialize a template for void parameter

I have a the following template and specialization (this code is not correct, but hopefully demonstrates my intent well enough):

template <typename T> widget &&make_widget(T &&val) { // (1)
    return std::move(widget(std::forward<T>(val)));
}

template <> widget &&make_widget(void) { // (2)
    return std::move(widget());
}

The intent is to have a factory function that can be called like this:

make_widget(arbitrary_function());

And have it choose the default constructor if arbitrary_function returns void.

However, with clang 3.7 I get the following error:

error: no function template matches function template specialization 'make_widget'

pointing to the line of (2). How can I implement this correctly?

Upvotes: 3

Views: 4273

Answers (2)

Sam Varshavchik
Sam Varshavchik

Reputation: 118330

As it's been pointed out, the problem is not really in the template specialization, but the usage of a void expression.

I can, however, suggest an alternative syntax that uses an intermediate lambda, and a helper template, to achieve the same results at a cost of a little bit of negligible extra usage syntax.

#include <functional>

class widget {

public:

    widget();
    widget(int);
};


template <typename T> widget &&do_make_widget(T &&val) { // (1)
    return std::move(widget(std::forward<T>(val)));
}

widget &&do_make_widget() { // (2)
    return std::move(widget());
}

template <typename T>
class do_make_widget_invoke {

public:

    template<typename functor_type>
    static auto invoke(functor_type &&functor)
    {
        return do_make_widget(functor());
    }
};

template <>
class do_make_widget_invoke<void> {

public:

    template<typename functor_type>
    static auto invoke(functor_type &&functor)
    {
        functor();
        return do_make_widget();
    }
};

template<typename functor_type>
auto make_widget(functor_type &&functor)
{
    return do_make_widget_invoke<decltype(functor())>
        ::invoke(std::forward<functor_type>(functor));
}

Testing the above with g++ 5.1.1 in -std=c++14 mode, I seem to get the right results with the following syntax, which is pretty much what you're trying to accomplish:

int foobar()
{
    return 0;
}


int main()
{
    make_widget([]{ return foobar(); });
}

and:

void foobar()
{
}


int main()
{
    make_widget([]{ return foobar(); });
}

Upvotes: 1

Brian Bi
Brian Bi

Reputation: 119164

You can't do this. It's impossible to create a function that has a parameter of type void. What you can do is make the function variadic, like make_unique.

template <typename... T>
widget make_widget(T&&... val) {
    return widget(std::forward<T>(val)...);
}

Then if you want to do something like

auto w = make_widget(void_function());

there is nothing stopping you from just doing instead:

void_function();
auto w = make_widget();

or even, if you really need it to be one statement for some reason,

auto w = (void_function(), make_widget());

Three further notes:

  • You should nix the std::move in the return statement since the result of calling the constructor is already an rvalue.
  • Never return a reference (even rvalue reference) to a temporary! It will always become a dangling reference.
  • There is no point to the make_widget function if forwarding its arguments to the widget constructor is the only thing it does. Note that make_unique takes care of calling new for you, and that make_tuple deduces the template arguments for the tuple itself. Your make_widget function doesn't do anything like that.

Upvotes: 3

Related Questions