c++reflectionmetaprogramming

c++ TMP reflection, type registration fail from within template function


I have one question with boost.pfr.

Anthony Polukhin made use of Alexandr Poltavsky(https://alexpolt.github.io/type-loophole.html)'s technique to register deduced struct field information. Basically, because C++ has no built-in reflection, Mr. Polukhin used converting operator 'template operator T(){...}' to detect fields of a class. For a POD aggregate class like

struct A{int val1;...};

He would use

struct U1{
       template<typename T>operator T(){...};
};

to initialize the struct like

A a{U1()};

for which U1's converting operator will be called and he resorts to Poltavsky's technique to save the deduced class. I speculate that this was because:

  1. In template metaprogramming (TMP) we cannot modify global list/registry/variable/storage as in runtime, and normally if we want to pass anything around we wrap it inside typelist/Hana tuple, essentially passing closures/continuations.
  2. But for converting operator, the return type is fixed (must correspond to actual field type), and we cannot write return type as a information-rich type like a tuple.
  3. There the only way was to 'jail break' from the converting operator and save the deduced field type with Poltavsky's loophole.

Poltavsky's loophole essentially

  1. first declares a function prototype without undetermined 'auto' return type.
  2. its definition is given as a friend function written inside another class templated on T, and inside the definition T{} is the return value. This is counterintuitive but allowed by (https://en.cppreference.com/w/cpp/language/friend.html) syntax (2) and the function is an independent function rather than a member function of the that class.

I appreciate both Polukhin and Poltavsky's ingenuities and try to experiment with their method, starting from simplest code snippets.

However I find that, Poltavsky's function works when we concretely specify a type parameter to 'loophole_t' class:

-----------------------------------good(clang++ -std=c++14)---------------------------------

using namespace std;
template <int N> struct tag {};
auto loophole(tag<0>);
template <typename T, int N> struct loophole_t {
  friend auto loophole(tag<N>) { return T{}; };
};
void f_Poltavsky() {
  /*https://alexpolt.github.io/type-loophole.html*/
  sizeof(loophole_t<std::string, 0>);
  cout << "Poltavsky:"
       << std::is_same<std::string, decltype(loophole(tag<0>{}))>::value
       << endl;
}
int main() {
  f_Poltavsky();
  return 0;
}

-----------------------------------good-----------------------------------------------

but never work when loophole_t is used inside a template function:

----------bad( error: function 'loophole' with deduced return type cannot be used before it is defined)-----------

using namespace std;
template <int N> struct tag {};
auto loophole(tag<1>);
template <typename T, int N> struct loophole_t {
  friend auto loophole(tag<N>) { return T{}; };
};
template <typename T>/*cannot use auto in because of C++14*/
static void f1(T x) {
  sizeof(loophole_t<T, 1>);
  cout << std::is_same<T, decltype(loophole(tag<1>{}))>::value
       << endl;
}
int main() {
  f1();
  return 0;
}

-----------------------------------bad-----------------------------------------------

I cannot figure out what is preventing it to work. If templated f1() does not work, how could it work inside any templated converting operator?

With respect, Jinn


Solution

  • You need some "adl" to resolve that:

    template <int N>
    struct tag {
        friend auto loophole(tag);
    };
    

    And make the call template dependent (to be called after instantiation of the template structure which define the function).

    template <typename T, int N = 0>
    static void f1(T) {
      sizeof(loophole_t<T, N>);
      std::cout << std::is_same<T, decltype(loophole(tag<N>{}))>::value << std::endl;
    }
    

    Demo