C++预处理器测试是否存在类成员

sab*_*lel 4 c++ c-preprocessor

是否有等效的#ifdef来测试一个成员是否存在于类中,以便可以在不导致代码使编译器失败的情况下完成处理.我尝试过模板操作,但特定问题没有成功.

例如

#if member baseclass.memberA()
  baseclass.memberA().push_back(data);
#else
  doAlternate(data);
#endif
Run Code Online (Sandbox Code Playgroud)

显然上面的内容是无效的,但我试图发现这样的东西是否已添加到C++ 11中

请注意,在初始设置中,将存在memberA,memberB,memberC,...每个都需要push_back.其他成员将来会被添加到基类中,这就是为什么我要创建一个模板,以便即使当前的基类没有某些成员(例如memberX),所有的情况都会正确编译和处理.否则,我可以使用一个非常简单的模板放入push_back()行.

这实际上是最简单的情况.还有一种情况,我创建子类的实例化,然后将其推回到子类成员.

// Instantiate an element of the Maindata class
::basedata::Maindata maindata;
//Instantiate an element of the Subdata class
::basedata::Subdata subinfo("This goes into the subinfo vector");
// Process some data that is part of the Subdata class
subinfo.contexts(contextInfo);
// Push the instantiated Subdata into the Subdata member of Maindata
maindata.subdata().push_back(subinfo);
Run Code Online (Sandbox Code Playgroud)

请注意,需要设置Subdata和subdata(),以便实现适当的代码.但是,如果:: basedata :: Subdata存在,那么maindata.subdata()也是如此.

我已经尝试过使用模板的各种方法,并且特定问题无法通过收到的各种答案解决.示例是类中存在的成员的模板实例化检查,C++类成员检查(如果不是模板),变量类型声明的C++模板

5go*_*der 5

这只是另一种情况void_t.

我们需要一个小帮助模板Void并定义一个便利模板类型别名void_t.

#include <type_traits>

template<typename...>
struct Void { using type = void; };

template<typename... T>
using void_t = typename Void<T...>::type;
Run Code Online (Sandbox Code Playgroud)

我们定义了实现回退策略的主模板.

template<typename T, typename = void>
struct Helper
{
  static void
  function(T& t)
  {
    std::cout << "doing something else with " << &t << std::endl;
  }
};
Run Code Online (Sandbox Code Playgroud)

并为支持特定操作的类型提供部分特化,在本例中.data().push_back(int).

template<typename T>
struct Helper<T, void_t<decltype(std::declval<T>().data().push_back(0))>>
{
  static void
  function(T& t)
  {
    std::cout << "pushing back data to " << &t << std::endl;
    t.data().push_back(42);
  }
};
Run Code Online (Sandbox Code Playgroud)

要隐藏Helper客户端的实现细节并允许模板参数的类型推导,我们可以很好地将其包装起来.

template<typename T>
void
function(T& t)
{
  Helper<T>::function(t);
}
Run Code Online (Sandbox Code Playgroud)

这就是我们的客户使用它的方式.

#include <iostream>
#include <vector>

class Alpha
{
public:
  std::vector<int>& data() { return this->data_; }
private:
  std::vector<int> data_ {};
};

class Beta { /* has no data() */ };

int
main()
{
  Alpha alpha {};
  Beta beta {};
  std::cout << "&alpha = " << &alpha << std::endl;
  std::cout << "&beta  = " << &beta << std::endl;
  function(alpha);
  function(beta);
}
Run Code Online (Sandbox Code Playgroud)

可能的输出:

&alpha = 0x7ffffd2a3eb0
&beta  = 0x7ffffd2a3eaf
pushing back data to 0x7ffffd2a3eb0
doing something else with 0x7ffffd2a3eaf
Run Code Online (Sandbox Code Playgroud)

更新:如何将此技术应用于多个成员

上面显示的技术可以应用于任何数量的成员.让我们举个例子吧.假设我们要编写一个模板函数frobnicate,该函数接受泛型类型的参数,如果该对象具有......

  • ...一个incr不带参数的成员函数,称之为,
  • ...数据成员name,如果可能的话,附加一些文本
  • ......一个数据成员numbers,push_back如果可能的话,还有一些数字.

真的建议你通过实现三个帮助器来解决这个问题,struct如上所示.它不是那么多冗余打字,而是使代码更清晰.

但是,如果您希望忽略此建议,那么让我们看看如何通过使用宏来减少键入.假设void_t上面显示的定义相同,我们可以定义以下宏.

#define MAKE_SFINAE_HELPER(NAME, TYPE, OPERATION, ARGS, CODE)           \
  template<typename TYPE, typename = void>                              \
  struct NAME                                                           \
  {                                                                     \
    template<typename... AnyT>                                          \
    void                                                                \
    operator()(AnyT&&...) noexcept                                      \
    {                                                                   \
      /* do nothing */                                                  \
    }                                                                   \
  };                                                                    \
                                                                        \
  template<typename TYPE>                                               \
  struct NAME<TYPE, void_t<decltype(std::declval<TypeT>()OPERATION)>>   \
  {                                                                     \
    void operator()ARGS noexcept(noexcept(CODE))                        \
    {                                                                   \
      CODE;                                                             \
    }                                                                   \
  };
Run Code Online (Sandbox Code Playgroud)

它将在类型参数上定义一个struct被调用的NAME模板,TYPE并使用一个运算符定义一个主模板,该运算符()可以接受任意类型的任意数量的参数,并且绝对不做什么.如果不支持所需的操作,则将其用作后备.

但是,如果类型的对象TYPE支持该操作OPERATION,则将使用带有()参数ARGS和执行的操作符的部分特化CODE.宏被定义为ARGS可以是带括号的参数列表.不幸的是,预处理器语法只允许将单个表达式传递为CODE.这不是一个大问题,因为我们总是可以写一个代理到另一个函数的函数调用.(请记住,计算机科学中的任何问题都可以通过添加额外的间接级别来解决 - 当然,除了间接级别太多的问题之外......)()部分特化的运算符将被声明,noexcept当且仅当它CODE是.(这也只能CODE用于单个表达式.)

()主模板的运算符是模板的原因是编译器可能会发出有关未使用变量的警告.当然,您可以更改宏以接受FALLBACK_CODE放置在主模板运算符主体中的其他参数,该运算符()应该使用相同的参数ARGS.

在最简单的情况下,有可能将参数OPERATIONCODE参数组合成一个但是然后CODE不能参考ARGS哪个有效地限制ARGS了单个参数类型,TYPE在这种情况下你也可以去除那个参数,如果你不这样做需要灵活性.

所以,让我们将其应用于我们的问题.首先,我们需要一个辅助函数来推回数字,因为这不能写成(至少,让我们假装这个)作为单个表达式.我使这个函数尽可能通用,只对成员名称做出假设.

template<typename ObjT, typename NumT>
void
do_with_numbers(ObjT& obj, NumT num1, NumT num2, NumT num3)
{
  obj.numbers.push_back(num1);
  obj.numbers.push_back(num2);
  obj.numbers.push_back(num3);
}
Run Code Online (Sandbox Code Playgroud)

由于其他两个所需的操作可以很容易地编写为单个表达式,因此我们不需要进一步间接它们.所以现在,我们可以生成我们的SFINAE助手.

MAKE_SFINAE_HELPER(HelperIncr,
                   TypeT,
                   .incr(),
                   (TypeT& obj),
                   obj.incr())

MAKE_SFINAE_HELPER(HelperName,
                   TypeT,
                   .name += "",
                   (TypeT& obj, const std::string& appendix),
                   obj.name += appendix)

MAKE_SFINAE_HELPER(HelperNumbers,
                   TypeT,
                   .numbers.push_back(0),
                   (TypeT& obj, int i1, int i2, int i3),
                   do_with_numbers(obj, i1, i2, i3))
Run Code Online (Sandbox Code Playgroud)

配备这些,我们终于可以编写我们的frobnicate功能.这很简单.

template<typename T>
void
frobnicate(T& object)
{
  HelperIncr<T>()(object);
  HelperName<T>()(object, "def");
  HelperNumbers<T>()(object, 4, 5, 6);
}
Run Code Online (Sandbox Code Playgroud)

为了看到一切正常,让我们做两个struct部分支持有问题的操作.

#include <string>
#include <vector>

struct Widget
{
  std::vector<int> numbers {1, 2, 3};
  int counter {};
  void incr() noexcept { this->counter += 1; }
};

struct Gadget
{
  std::string name {"abc"};
  int counter {};
  void incr() noexcept { this->counter += 1; }
};
Run Code Online (Sandbox Code Playgroud)

由于我想打印它们,我们还要定义运算符<<.

#include <iostream>

std::ostream&
operator<<(std::ostream& os, const Widget& w)
{
  os << "Widget : { counter : " << w.counter << ", numbers : [";
  int i {};
  for (const auto& v : w.numbers)
    os << (i++ ? ", " : "") << v;
  os << "] }";
  return os;
}

std::ostream&
operator<<(std::ostream& os, const Gadget& g)
{
  os << "Gadget : { counter : " << g.counter << ", "
     << "name = \"" << g.name << "\" }";
  return os;
}
Run Code Online (Sandbox Code Playgroud)

然后我们去:

int
main()
{
  Widget widget {};
  Gadget gadget {};
  std::cout << widget << "\n" << gadget << "\n\n";
  frobnicate(widget);
  frobnicate(gadget);
  std::cout << widget << "\n" << gadget << "\n";
}
Run Code Online (Sandbox Code Playgroud)

输出:

Widget : { counter : 0, numbers : [1, 2, 3] }
Gadget : { counter : 0, name = "abc" }

Widget : { counter : 1, numbers : [1, 2, 3, 4, 5, 6] }
Gadget : { counter : 1, name = "abcdef" }
Run Code Online (Sandbox Code Playgroud)

我鼓励您仔细衡量这种宏观方法的成本和收益.在我看来,额外的复杂性几乎不值得打字的小额节省.