Making sfinae works for functions with deduced ret

2019-07-13 02:56发布

问题:

Consider the following code:

// -------------------------------------------------------------------------- //
// Preprocessor
#include <array>
#include <vector>
#include <utility>
#include <iostream>
#include <type_traits>
// -------------------------------------------------------------------------- //



// -------------------------------------------------------------------------- //
// Calls a function without arguments if it can be called
template <
    class F, 
    class... Args,
    class = decltype(std::declval<F>()())
>
decltype(auto) apply(F&& f)
{
    std::cout<<"apply(F&& f)"<<std::endl;
    return std::forward<F>(f)();
} 

// Calls a function with arguments if it can be called
template <
    class F, 
    class Arg,
    class... Args,
    class = decltype(std::declval<F>()(
        std::declval<Arg>(), std::declval<Args>()...
    ))
>
decltype(auto) apply(F&& f, Arg&& arg, Args&&... args)
{
    std::cout<<"apply(F&& f, Arg&& arg, Args&&... args)"<<std::endl;
    return std::forward<F>(f)(
        std::forward<Arg>(arg), 
        std::forward<Args>(args)...
    );
} 

// Does nothing if the function cannot be called with the given arguments
template <
    class F, 
    class... Args
>
void apply(F&& f, Args&&... args)
{
    std::cout<<"apply(F&& f, Args&&... args)"<<std::endl;
}
// -------------------------------------------------------------------------- //



// -------------------------------------------------------------------------- //
// Main function
int main(int argc, char* argv[])
{
    // Initialization
    auto f = [](auto&& x) -> decltype(std::forward<decltype(x)>(x).capacity()) {
        return std::forward<decltype(x)>(x).capacity();
    };
    auto g = [](auto&& x) -> decltype(auto) {
        return std::forward<decltype(x)>(x).capacity();
    };
    auto h = [](auto&& x) {
        return std::forward<decltype(x)>(x).capacity();
    };

    // Test
    apply(f, std::vector<double>());  // -> sfinae works
    apply(g, std::vector<double>());  // -> sfinae works
    apply(h, std::vector<double>());  // -> sfinae works
    apply(f, std::array<double, 1>());// -> sfinae works
    //apply(g, std::array<double, 1>()); -> sfinae fails, does not compile
    //apply(h, std::array<double, 1>()); -> sfinae fails, does not compile

    // Return
    return 0;
}
// -------------------------------------------------------------------------- //

The utility apply, applies a function to arguments when it can compile, otherwise it does nothing. The mechanism relies on sfinae. However, for function whose return type is deduced from the body, like g and h in the above example, sfinae fails. Would there be a smart way in C++14 to modify the apply utility so that it forces sfinae to kicks in even for functions whose return type is deduced from the body?

Note: I would like to make g and h work as f, meaning that the call of apply should call the void version.

回答1:

SFINAE can only catch substitution errors.

Some errors when calling a function cannot be substitution errors. These include errors that happen when you parse the body of a function.

C++ explicitly chose to exclude these errors from triggering SFINAE and instead trigger hard errors to free compilers from having to compile bodies of arbitrary functions to determine if SFINAE occurs. As SFINAE must be done during overload resolution, this makes the overload resolution code of a C++ compiler easier.

If you want your code to be SFINAE friendly, you cannot use lambdas like g or h.



回答2:

Your SFINAE works just fine, as regards to apply.

As to why f works, it's because:

  1. It's a template and

  2. It fails already in the substitution phase.

This means it has SFINAE of it's own, and for f the default do-nothing is called (for array), as intended.


Would there be a smart way in C++14 to modify the apply utility so that it forces...

No, apply already does all it should.

As to why g() and h() does not work, they will produce hard errors when you call them with something that does not have capacity. A utility function you (try to) apply after that can not remove that hard error. The only way to fix this is "double whammy" SFINAE, like you do in f()