在编译期间检测C++中的函数

6

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

我想特别应用一个函数,如果对象有该函数,则输出对象的尺寸(即集合中对象的数量),但如果对象没有该函数,则输出一个简单的替换(如“N/A”)。例如:

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

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


可能是重复问题:https://dev59.com/NHVC5IYBdhLWcg3wjx1d - Alessandro Pezzato
1
@AlessandroPezzato:随着C++11的到来,有一些尚未在链接答案中探索的新机会。 - Matthieu M.
感谢大家提供的所有答案和建议。不幸的是,由于客户的限制,我无法在我的解决方案中使用C++11或Boost,这很遗憾。尽管如此,有了你们提供的信息,我能够让它运行并扩展它,以提供一个函数来流输出size()或"<N/A>",使用一个私有的、嵌入式的模板类,部分专门为“真”条件。现在我非常高兴!谢谢大家! - Component 10
6个回答

13
据我所知,您想要一个通用的测试来检查一个类是否具有特定的成员函数。在C++中,可以使用SFINAE来实现这一点。在C++11中,由于可以使用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,这使得它有点困难:
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非常简单:
template<bool Cond, typename T> enable_if;
template<typename T> enable_if<true, T> { typedef T type; };

在这两种情况下,如果U没有一个size方法,那么只有当方法签名test(int)可见时,依据SFINAE原则,将会从考虑中移除该方法。std::declval().size(), void(), std::true_type()是对C++逗号运算符的滥用,它将返回逗号分隔列表中的最后一个表达式,因此这可以确保类型在C++11版本中被知道为std::true_type(并且sizeof在C++03版本中评估为int)。中间的void()只是为了确保没有奇怪的逗号运算符重载干扰评估。
当然,如果 T 有一个可无需参数调用的 size 方法,则此方法将返回 true,但不能保证返回值。我假设您可能只想检测那些不返回 void 的方法。可以通过轻微修改 test(int) 方法来轻松实现此目标:
// 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);

有趣...但是void()是干嘛用的? - Matthieu M.
@MatthieuM:void()并不是必需的。但是它存在的目的是为了确保在重载逗号运算符时不会发生奇怪的事情(虽然在这种情况下不太可能,但这是我检查所有方法是否存在的一般方式,所以我想保留它)。 - Grizzly
我曾经简要地认为可能是这种情况,但看起来更像是在添加 true_type 时遗忘了一些东西 :) 至于采用 decltype 的方式,对于函数来说,它使事情变得非常容易,因此更容易定义特性为 constexpr 函数或直接绕过特性并直接使用它,请参见我的答案 :) - Matthieu M.
这个和C++ 11的(https://en.cppreference.com/w/cpp/types/is_member_function_pointer)有什么不同? - undefined

10

不久前,有关于constexpr能力的讨论。我认为现在是使用它的时候了 :)

使用constexprdecltype设计特征(trait)非常容易:

template <typename T>
constexpr decltype(std::declval<T>().size(), true) has_size(int) { return true; }

template <typename T>
constexpr bool has_size(...) { return false; }

实际上这一特点变得非常容易,以至于它失去了大部分的价值:

#include <iostream>
#include <vector>

template <typename T>
auto print_size(T const& t) -> decltype(t.size(), void()) {
  std::cout << t.size() << "\n";
}

void print_size(...) { std::cout << "N/A\n"; }

int main() {
  print_size(std::vector<int>{1, 2, 3});
  print_size(1);
}

实例展示

3
N/A

我能看到Grizzly的回答中使用了void(),但是在这里呢?似乎是多余的,因为无论如何都不会有第二个参数传递给“可能”的逗号运算符。是一个遗留问题吗? - Xeo
我不确定这是否是编写特征的更好方式。与将其封装在结构体中的“常规”方法相比,您只是避免了在方法周围编写结构体,并将结果值放入枚举或constexpr bool中。另一方面,在我看来,has_size以这种方式具有较不理想的签名。对于print_size也是如此。 - Grizzly
@Xeo:print_size函数中的void()用于将其返回值设置为void,而不是decltype(declval<T>().size()) - Grizzly
@Grizzly:我认为这是一种不同的方式。它肯定是新的,所以我预计会有一些阻力。我在这里给出了一些原因,为什么 traits 可以移动到函数中。几年前,我可能会接受你的类,但现在我正在尝试探索 C++11 的可能性,特别是在减少混乱和获得新功能方面。 - Matthieu M.
请注意,您可以通过使用尾随返回类型来进一步减少混乱:auto print_size(T const& t) -> decltype(t.size(), void()) - Xeo

4

可以使用一种称为 SFINAE 的技术来实现。在您的特定情况下,您可以使用 Boost.Concept Check 实现它。您需要编写自己的概念以检查 size 方法。或者,您可以使用现有的概念,例如Container,其中包括一个size方法。


我很惊讶这个答案没有获得最高票数。在我看来,SFINAE是这里的正确方法。 - lapk
1
@AzzA:尽管我不喜欢“你可以像<code>这样做”的回答风格,但这里的大多数回答实际上展示了SFINAE实践... - Sebastian Mach

3
你可以做如下操作:
template< typename T>
int getSize(const T& t)
{
    return -1;
}

template< typename T>
int getSize( const std::vector<T>& t)
{
    return t.size();
}

template< typename T , typename U>
int getSize( const std::map<T,U>& t)
{
    return t.size();
}

//Implement this interface for 
//other objects
class ISupportsGetSize
{
public:
    virtual int size() const= 0;
};

int getSize( const ISupportsGetSize & t )
{
    return t.size();
}

int main()
{
    int s = getSize( 4 );
    std::vector<int> v;
    s = getSize( v );

    return 0;
}

基本上最通用的实现是始终返回-1或“NA”,但对于向量和映射,它将返回大小。由于最普遍的匹配始终匹配,因此永远不会发生构建时失败。


2

请看这里。std::cout替换为您喜欢的输出。

template <typename T>
class has_size
{
    template <typename C> static char test( typeof(&C::size) ) ;
    template <typename C> static long test(...);

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

template<bool T>
struct outputter
{
    template< typename C >
    static void output( const C& object )
    {
        std::cout << object.size();
    }
};

template<>
struct outputter<false>
{
    template< typename C >
    static void output( const C& )
    {
        std::cout << "N/A";
    }
};


template<typename T>
void NO_OF_ELEMENTS( const T &object )
{
    outputter< has_size<T>::value >::output( object );
}

这样做行不通。object.size() 会导致失败。你需要将打印委托给一个 SFINAE 结构。 - Matthieu M.
@MatthieuM。是的!我忽略了那个。谢谢。现在已经修复了。 - Drew Dormann

1
你可以尝试这样做:
#include <iostream>
#include <vector>



template<typename T>                                                                
struct has_size                                                                 
{                                                                                   
  typedef char one;                                                                 
  typedef struct { char a[2]; } two;                                                

  template<typename Sig>                                                            
  struct select                                                                     
  {                                                                                 
  };                                                                                

  template<typename U>                                                              
  static one check (U*, select<char (&)[((&U::size)!=0)]>* const = 0);     
  static two check (...);                                                           

  static bool const value = sizeof (one) == sizeof (check (static_cast<T*> (0)));   
};



struct A{ };
int main ( )
{
    std::cout << has_size<int>::value << "\n";
    std::cout << has_size<A>::value << "\n";
    std::cout << has_size<std::vector<int>>::value << "\n";
}

但是你必须小心,当size被重载或者是一个模板时,这个方法都不适用。如果你使用C++11,你可以用decltype魔法来替换上面的sizeof技巧。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接