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

时间:2015-02-26 17:26:06

标签: c++ c-preprocessor

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

例如

#if member baseclass.memberA()
  baseclass.memberA().push_back(data);
#else
  doAlternate(data);
#endif

显然上面的内容无效,但我试图发现是否有类似内容添加到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);

请注意,需要设置Subdata和subdata(),以便实现相应的代码。但是,如果:: basedata :: Subdata存在,则maindata.subdata()也将存在。

我已经尝试过使用模板的各种方法,并且特定问题无法通过收到的各种答案解决。示例包括template instantiation check for member existing in classC++ class member check if not a templateC++ template for variable type declaration

3 个答案:

答案 0 :(得分: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;

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

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

为支持特定操作的类型提供部分特化,在本例中为.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);
  }
};

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

template<typename T>
void
function(T& t)
{
  Helper<T>::function(t);
}

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

#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);
}

可能的输出:

&alpha = 0x7ffffd2a3eb0
&beta  = 0x7ffffd2a3eaf
pushing back data to 0x7ffffd2a3eb0
doing something else with 0x7ffffd2a3eaf

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

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

  • ...不带参数的成员函数incr,称之为
  • ...数据成员name,如果可能,请附加一些文字
  • ...数据成员numberspush_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;                                                             \
    }                                                                   \
  };

它将在类型参数struct上定义一个名为NAME的{​​{1}}模板,并定义一个带有运算符TYPE的主模板,该模板可以接受任意类型的任意数量的参数绝对没有。如果不支持所需的操作,则将其用作后备。

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

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

在最简单的情况下,可以将()ARGS参数合并为一个,但OPERATION无法引用有效限制CODE的{​​{1}} {1}}到CODE类型的单个参数,在这种情况下,如果您不需要灵活性,也可以删除该参数。

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

ARGS

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

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);
}

要看到一切正常,让我们制作两个部分支持相关操作的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))

frobnicate

由于我想要打印它们,我们还要定义运算符template<typename T> void frobnicate(T& object) { HelperIncr<T>()(object); HelperName<T>()(object, "def"); HelperNumbers<T>()(object, 4, 5, 6); }

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; }
};

输出:

<<

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

答案 1 :(得分:3)

不使用预处理器,但以下内容可能有所帮助:

#include <cstdint>

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(has_memberA, T::memberA, std::vector<int> (T::*)(void));

然后,使用SFINAE:

#include <type_traits>

template <typename T>
std::enable_if<has_memberA<T>::value>::type
doBase(T& t, int data)
{
    t.memberA().push_back(data);
}

template <typename T>
std::enable_if<!has_memberA<T>::value>::type
doBase(T& , int data)
{
    doAlternate(data);
}

答案 2 :(得分:0)

我终于根据这个和其他问题的答案找到了答案,我真的有两个问题需要两个不同的答案。

我有许多具有不同成员的类,所有类都具有以下类型的操作

::basedata::Maindata maindata;
maindata.subdata().push_back(subinfo);
auto inData maindata.subdata().back();
inData.contexts(contextInfo);

模板的形式(假设类subdata()确实存在(例如问题中的memberA)。

template<typename T, typename D>
auto doPush(D myData, T & myFormat, contextType contextInfo)
  -> decltype(myFormat.push_back(myData), bool())
{
  myFormat.push_back(myData);
  setcontexts(myFormat.back(), contextInfo)
  // Since the push-back() was already done, the new data gets entered
  return true;
}

这样就可以调用模板

doPush(dataset, maindata.subdata(), contextInfo);

由于这假设存在subdata(),我们现在需要为子数据表示的成员设置显式测试,并使其成为围绕通用模板调用的瘦包装。

template<typename T, typename D>
auto createMember(D myData, T & myFormat, contextType contextInfo)
  -> decltype(myFormat.Member(), bool())
{
  dopush(myData, myFormat.Member(), myData);
  return true;
}

请注意,如果有足够的位置需要,可以通过宏输入成员名称只需要三个位置。

然后,实际代码将调用createMember模板。

这似乎是最简单的解决方案。

我没有展示虚假案例模板,因为那些很明显。