在编译时检测 C++ 中的函数

2022-01-11 00:00:00 static macros templates c++

有没有一种方法,大概是使用模板、宏或两者的组合,我可以将一个函数一般地应用于不同类的对象,但如果它们没有特定的函数,它们会以不同的方式响应?

Is there a way, presumably using templates, macros or a combination of the two, that I can generically apply a function to different classes of objects but have them respond in different ways if they do not have a specific function?

我特别想应用一个函数,如果对象具有该函数,该函数将输出对象的大小(即集合中的对象数量),但如果对象具有该函数,则会输出一个简单的替换(例如N/A"),如果对象没有.即

I specifically want to apply a function which will output the size of the object (i.e. the number of objects in a collection) if the object has that function but will output a simple replacement (such as "N/A") if the object doesn't. I.e.

NO_OF_ELEMENTS( mySTLMap ) -----> [ calls mySTLMap.size() to give ] ------>  10
NO_OF_ELEMENTS( myNoSizeObj ) --> [ applies compile time logic to give ] -> "N/A"

我希望这可能类似于静态断言,尽管我显然希望编译不同的代码路径而不是在构建阶段失败.

I expect that this might be something similar to a static assertion although I'd clearly want to compile a different code path rather than fail at build stage.

推荐答案

据我了解,你希望有一个通用测试来查看一个类是否具有某个成员函数.这可以使用 SFINAE 在 C++ 中完成.在 C++11 中它非常简单,因为您可以使用 decltype:

From what I understand, you want to have a generic test to see if a class has a certain member function. This can be accomplished in C++ using SFINAE. In C++11 it's pretty simple, since you can use decltype:

template <typename T>
struct has_size {
private:
    template <typename U>
    static decltype(std::declval<U>().size(), void(), std::true_type()) test(int);
    template <typename>
    static std::false_type test(...);
public:
    typedef decltype(test<T>(0)) type;
    enum { value = type::value };
};

如果你使用C++03,由于缺少decltype,会有点困难,所以你不得不滥用sizeof来代替:

If you use C++03 it is a bit harder due to the lack of decltype, so you have to abuse sizeof instead:

template <typename T>
struct has_size {
private:
    struct yes { int x; };
    struct no {yes x[4]; };
    template <typename U>
    static typename boost::enable_if_c<sizeof(static_cast<U*>(0)->size(), void(), int()) == sizeof(int), yes>::type test(int);
    template <typename>
    static no test(...);
public:
    enum { value = sizeof(test<T>(0)) == sizeof(yes) };
};

当然,这使用 Boost.Enable_If,这可能是一个不需要的(和不必要的)依赖项.然而,自己编写 enable_if 非常简单:

Of course this uses Boost.Enable_If, which might be an unwanted (and unnecessary) dependency. However writing enable_if yourself is dead simple:

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

在这两种情况下,方法签名 test<U>(int) 仅在 U 具有 size 方法时才可见,否则评估 decltypesizeof (取决于您使用的版本)将失败,然后将从考虑中删除该方法(由于 SFINAE. 冗长的表达式 std::declval().size(), void(), std::true_type() 是对 C++ 逗号运算符的滥用,会返回最后一个表达式从逗号分隔的列表中,所以这确保类型被称为 C++11 变体的 std::true_type (并且 sizeof 评估 int 用于 C++03 变体).中间的 void() 仅用于确保不存在干扰评估的逗号运算符的奇怪重载.

In both cases the method signature test<U>(int) is only visible, if U has a size method, since otherwise evaluating either the decltype or the sizeof (depending on which version you use) will fail, which will then remove the method from consideration (due to SFINAE. The lengthy expressions std::declval<U>().size(), void(), std::true_type() is an abuse of C++ comma operator, which will return the last expression from the comma-separated list, so this makes sure the type is known as std::true_type for the C++11 variant (and the sizeof evaluates int for the C++03 variant). The void() in the middle is only there to make sure there are no strange overloads of the comma operator interfering with the evaluation.

当然,如果 T 有一个可以不带参数调用的 size 方法,但不保证返回值,这将返回 true.我假设你可能只想检测那些不返回 void 的方法.这可以通过对 test(int) 方法的轻微修改轻松完成:

Of course this will return true if T has a size method which is callable without arguments, but gives no guarantees about the return value. I assume wou probably want to detect only those methods which don't return void. This can be easily accomplished with a slight modification of the test(int) method:

// C++11
template <typename U>
static typename std::enable_if<!is_void<decltype(std::declval<U>().size())>::value, std::true_type>::type test(int);
//C++03
template <typename U>
static typename std::enable_if<boost::enable_if_c<sizeof(static_cast<U*>(0)->size()) != sizeof(void()), yes>::type test(int);

相关文章