使用省略号的回退函数:我们可以强制参数包的大小吗?

Fallback function using ellipsis: can we force the size of the parameters pack?

考虑以下代码:

#include <utility>
#include <iostream>

struct S {
    template<typename T, typename... A>
    auto f(A&&... args) -> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) {
        std::cout << "has f(int)" << std::endl;
    }

    template<typename>
    void f(...) {
        std::cout << "has not f(int)" << std::endl;
    }
};

struct T { void f(int) { } };
struct U { };

int main() {
    S s;
    s.f<T>(42); // -> has f(int)
    s.f<U>(42); // -> has not f(int)
    // oops
    s.f<T>(); // -> has not f(int)
}

如示例所示,对 f 的第三次调用工作正常,即使参数数量 错误 ,因为对于回退功能。

有没有办法在涉及省略号时强制参数的数量?
我的意思是,我可以在编译时检查参数列表的大小是否正好 1,而不管选择的是主函数还是后备函数?

好的解决方案也是那些只涉及第一个模板函数并且由于参数包的大小而导致硬错误而不是软错误的解决方案。


当然,它可以通过多种技术来解决,而无需使用可变参数。例如:int/char 调度内部模板方法;明确指定参数列表;随便...
问题不在于替代方法,我已经知道了。
只是想知道我是否遗漏了一些基本的东西,或者这是不可能的,仅此而已。

如果我理解正确你的问题,你可以加一层:

struct S {
private:
    template<typename T, typename... A>
    auto f_impl(A&&... args)
    -> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) {
        std::cout << "has f(int)" << std::endl;
    }

    template<typename>
    void f_impl(...) {
        std::cout << "has not f(int)" << std::endl;
    }
public:

    template<typename T, typename A>
    auto f(A&& args) { return f_impl<T>(std::forward<A>(arg)); }
};

有了特质,你可以做到

template <typename T, typename ... Ts>
using f_t = decltype(std::declval<T>().f(std::declval<Ts>()...));

template <typename T, typename ... Ts>
using has_f = is_detected<f_t, T, Ts...>;

struct S {
    template<typename T, typename... A>
    std::enable_if_t<has_f<T, A&&...>::value && sizeof...(A) == 1> f(A&&... args)
    {
        std::cout << "has f(int)" << std::endl;
    }

    template<typename T, typename... A>
    std::enable_if_t<!has_f<T, A&&...>::value && sizeof...(A) == 1>  f(A&&... args) {
        std::cout << "has not f(int)" << std::endl;
    }
};

Demo

您可以使用获取指向函数的指针的函数(断言)来推断参数的大小:

#include <utility>
#include <iostream>
template <typename...Args>
struct size_assert{
    template <typename T,typename R,typename... Params>
    constexpr static bool assert(R(T::*)(Params...) )
    {
        static_assert(sizeof...(Args) == sizeof...(Params),"Incorrect size of arguments!");
        return true;
    }
};

struct S {

    template<typename T, typename... A, bool =  size_assert<A...>::assert(&T::f)>
    auto f(A&&... args) -> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) 

    {
        std::cout << "has f(int)" << std::endl;
    }

    template<typename>
    void f(...) {
        std::cout << "has not f(int)" << std::endl;
    }
};


struct T { void f(int) { } };
struct U { };

int main() {
   // std::cout <<fc(&f);
    S s;
    s.f<T>(42); // -> has f(int)
    s.f<U>(42); // -> has not f(int)
    // oops
    s.f<T>(); // -> has not f(int)
}