функция-член шаблона, специализирующаяся на указателе на элемент-данные

Я хотел бы определить нулевую статическую функцию-член шаблона, которая (явно) специализировалась бы на указателе на элемент данных и могла бы иметь для каждой специализации другой тип возврата.

Он должен возвращать подробную информацию о каждом атрибуте, поэтому я назову этот метод trait. Возвращенный тип трейт-объекта будет проверяться другими шаблонами, поэтому весь этот механизм должен быть доступен во время компиляции.

Пока у меня есть что-то вроде этого (сломанный код, конечно):

class Foo{
   // some data members
   int a; std::string b; int c;
   // how to declare generic template here?
   // compile-time error should ensue if none of the specializations below is matched

   // specialization for a (aTraitExpr is expanded from macro, so it is OK to repeat it)
   template auto trait<&Foo::a>()->decltype(aTraitExpr){ return aTraitExpr; }
   // specialization for b; the return type will be different than for trait<&Foo::a>
   template auto trait<&Foo::b>()->decltype(bTraitExpr){ return bTraitExpr; }
};

// some code which queries the trait at compile-time
// e.g. supposing all possible trait types declare isSerializable
// which happens to be True for a and False for b

Foo* foo;
template<bool isSerializable> void doSerialization(...);
template void doSerialization<true>(...){ ... };
template void doSerialization<false>(...){ /* no-op */ };

doSerialization<Foo::trait<&Foo::a>()::isSerializable>(...); // -> doSerialization<true>(foo)
doSerialization<Foo::trait<&Foo::b>()::isSerializable>(...); // -> doSerialization<False>(...)
doSerialization<Foo::trait<&Foo::c>()::isSerializable>(...); // -> compile error, specialization Foo::trait<&Foo::c> not defined

Можно подсказать, как этого добиться? (Я не пытаюсь изобретать новую систему сериализации, я уже использую boost::serialization; в каждом трейте будет больше информации, это просто для примера, зачем это нужно во время компиляции).

РЕДАКТИРОВАТЬ: мне удалось получить что-то близкое к тому, что я хочу, это показано на на сайте ideone.com. Я отказался от trait<Foo::a>() (на данный момент), поэтому есть статическая функция getTrait_a(), которая возвращает ссылку на модифицируемые признаки типа, которые, однако, частично фиксируются во время компиляции (так что, например, Foo::TraitType_a::flags работает). Спасибо всем, кто ответил, к сожалению, я могу выбрать только один из ответов как «ответ».


person eudoxos    schedule 13.05.2012    source источник


Ответы (3)


Похоже, вам нужно несколько перегрузок вместо специализаций. К сожалению, вы не уточняете, что такое xTraitExpr, но кажется, что это просто тип, в котором определен член isSerializable. Я бы, наверное, пошел так

class Foo {
   // your members have been omitted to save space...

   template<typename T, T Foo::*M>
   struct W { };

   static decltype(aTraitExpr) trait(W<int, &Foo::a>) {
     return aTraitExpr;
   }

   static decltype(bTraitExpr) trait(W<std::string, &Foo::b>) {
     return bTraitExpr;
   }

   // other overloads for other members...

public:
   // overloads for each member type
   template<int Foo::*M> 
   static decltype(trait(W<int, M>())) trait() { 
     return trait(W<int, M>()); 
   }

   template<std::string Foo::*M> 
   static decltype(trait(W<std::string, M>())) trait()  { 
     return trait(W<std::string, M>()); 
   }
};

trait(W<M>()) является зависимым вызовом. Зависимый вызов выполняет ADL во время определения и создания экземпляра, а неквалифицированный поиск — только во время определения. Вот почему W и дополнительные перегрузки trait, использующие его, должны быть определены до перегрузок типа trait, а не после них, иначе результат разрешения в возвращаемом типе и теле функций будет другим, поскольку они анализируются в разное время (тела поздно анализируются после определения класса, а возвращаемые типы анализируются немедленно).

Вы можете сделать trait функцией constexpr и сделать xTraitExpr литеральным классом с конструктором constexpr, соответствующим образом инициализирующим isSerializable, или вы можете применить decltype следующим образом.

doSerialization<decltype(Foo::trait<&Foo::a>())::isSerializable>(...);
person Johannes Schaub - litb    schedule 13.05.2012
comment
xTraitExpr это Trait<compile-time-params>().modifier1().modifier2(), и мне нужно извлечь из него параметры времени компиляции. Извините, что не разъяснил, не хотел слишком усложнять вопрос. - person eudoxos; 13.05.2012

Я думаю, что здесь нет смысла использовать шаблон функции. При этом использование шаблона класса вместо него также не очень удобно: вы должны учитывать тот факт, что нестатические элементы данных могут иметь разные типы и может быть несколько нестатических элементов данных с одним и тем же типом. . Вот возможность:

template<typename T>
struct is_serializable: std::false_type {};

struct Foo {
    int a; std::string b; int c;

    // Primary template left undefined on purpose
    // alternatively, could use a static_assert on a dependent
    // std::false_type::value for better diagnostics
    template<typename T, T t>
    struct attribute_trait;
};

// Define explicit specializations outside of class
template<>
struct Foo::attribute_trait<int Foo::*, &Foo::a>
: is_serializable<int> {};

template<>
struct Foo::attribute_trait<std::string Foo::*, &Foo::b>
: is_serializable<std::string> {};

Который можно использовать как

doSerialization<Foo::attribute_trait<decltype(&Foo::a), &Foo::a>::value>(/* stuff */);
person Luc Danton    schedule 13.05.2012

Обычный способ определить класс свойств — оборачивать структуру/класс вокруг константного выражения времени компиляции (а не оборачивать функцию, возвращающую такое выражение). Синтаксис для получения функции-члена класса выглядит следующим образом:

template
<
    SomeReturnType (SomeClass::*SomeMemberFunction)(SomeParameters)
>
class SomeTrait
{
    static const value = SomeCompileTimeConstantExpression;
};

В вашем случае вы бы сделали это так:

template
<
    void (Foo::*f)()
>
class trait
{
    static const value = fTraitExpr;
};

Затем вы специализируете этот трейт для всех функций-членов class Foo:

template<>
class trait<&Foo::a>
{
    static const value = aTraitExpr;
};

// same for Foo::b and Foo::c

Кроме того, более идиоматично перегружать шаблоны функций, чем специализировать их:

template<int V> struct Int2Type { enum { value = V }; };

Foo* foo;

template
<
    void (Foo::*f)()
>
void doSerialization(...)
{
    dispatch::doSerialization(Int2Type< trait<f>::value >(), ...);
}

namespace dispatch {

doSerialization(Int2Type< true >, ...) { ... };

doSerialization(Int2Type< false >, ...) { /* no-op */ };

} // namespace dispatch

Затем вы можете назвать это так:

doSerialization<&Foo::a>(...);
doSerialization<&Foo::b>(...);
doSerialization<&Foo::c>(...);
person TemplateRex    schedule 13.05.2012