Brad
Brad

Reputation: 3540

How to handle void decltype();

I'd like to create a template that calls a member function on another object that returns the same type as the member function. The syntax for using decltype on the member function is a bit ugly, but it seems to work in every case except one.

Here's the call:

struct container: map<string, C> 
{
    template< typename MemFnPtrType, typename... _ArgTypes>
    auto safeOperation(string key, MemFnPtrType mfp, _ArgTypes&&... args )
        -> decltype( (((C*)nullptr)->*mfp)(args...))
    {
        C* pC = NULL;
        decltype((pC->*mfp)(args...)) result;

        iterator it = find(key);
        if (it != end())
        {
            C* pC = &(it->second);
            result = (pC->*mfp)(args...);
            cout << "result:" << result << "\n";
        }
        else
        {
            cout << "key: " << key << " missing\n";
        }
        return result;
    }
};

This works fine until the member function returns void.

Is there a way to detect this and leave out the offending lines?

I could obviously create a voidSafeOperation function. I don't mind creating another template, but I'd like to use the same name "safeOperation" so the call sites don't need to use different helpers based on the return type of the member function.

Thanks!

Full Example: http://cpp.sh/7ft

Upvotes: 4

Views: 1843

Answers (2)

KeyC0de
KeyC0de

Reputation: 5287

It is a corner case. auto with decltype for trailing return type don't work for a void return type because void is an incomplete type.

example:

auto nothingMuch()
{
    return;
}

// nope
//decltype(void {}) nothingMuchEither()
//{
//  return;
//}

// nope
//auto noTrailing() -> decltype(void {})
//{
//
//}

// always works
decltype(auto) nothing()
{
    return;
}

auto main() -> decltype(int {})
{
    nothing();
    nothingMuch();
    return 0;
}

The easiest fix is to replace auto and decltype in the trailing return type with decltype(auto) as the return type (needs C++14).

Upvotes: 1

Barry
Barry

Reputation: 304122

Unfortunately, I think you're stuck here with having to SFINAE on the return type. Start with a type trait (this is way cleaner than your decltype expression)

template <typename MF, typename... Args>
using Res = typename std::result_of<MF(C, Args...)>::type;

And then just switch:

template <typename MF, typename... Args>
typename std::enable_if<
    std::is_same<Res<MF, Args...>, void>::value
>::type safeOperation(string key, MF mfp, Args... args)
{
     /* void case */
}

template <typename MF, typename... Args>
typename std::enable_if<
    !std::is_same<Res<MF, Args...>, void>::value,
    Res<MF, Args...>
>::type safeOperation(string key, MF mfp, Args... args)
{
     /* non-void case */
}

Or you could tag dispatch on is_void:

template <typename MF, typename... Args>
Res<MF, Args...> safeOperation(string key, MF mfp, Args... args)
{
    return safeOperation(std::is_void<Res<MF, Args...>>{},
                         key, mfp, args...);
}

with:

template <typename MF, typename... Args>
void safeOperation(std::true_type /* void */, 
                   string key, MF mfp, Args... args) 
{ .. }

template <typename MF, typename... Args>
Res<MF, Args...> safeOperation(std::false_type /* non-void */, 
                               string key, MF mfp, Args... args) 
{ .. }

Upvotes: 9

Related Questions