How can I make C++ ADL to look into all instances of a template?
Asked Answered
G

1

23

I'm following the How to implement a constant-expression counter in C++ tutorial and I'm trying to fix the C++14 Reflections Without Macros, Markup nor External Tooling talk limitations.

The basic idea of the tutorial is this:

template<int N>
struct flag {
  friend constexpr int adl_flag (flag<N>);
};

template<int N>
struct writer {
   friend constexpr int adl_flag (flag<N>) { return N; }
   static constexpr int value = N;
};

template<int N, class = char[noexcept(adl_flag(flag<N> ()))?+1:-1]>
int constexpr reader (int, flag<N>) { return N; }

template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1>())) { return R; }

int constexpr reader (float, flag<0>) { return 0; }

template<int N = 1, int C = reader (0, flag<32> ())>
int constexpr next (int R = writer<C + N>::value) { return R; }

int main () {
  constexpr int a = next ();
  constexpr int b = next ();
  constexpr int c = next ();

  // YES! it works!!!
  static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}

Note: If you are not interested by now, it's a good time to stop reading :-)

And the talk explains how to extract number of fields and fields types for POD types using aggregate initialization and implicit conversion operator, but the major limitation is that only primitive types are supported.

I've provided the above background to justify my motivations!

When I combined these two approaches I came to this:

template<int N>
struct flag {
  friend constexpr int adl_flag (flag<N>);
};

template<typename T, int N>
struct writer {
  friend constexpr int adl_flag (flag<N>) {
    return N;
  }
  friend constexpr T field_type(flag<N>) { return T{}; }
  static constexpr int value = N;
};

The field_type(flag<N>) will give me the type of Nth field. Note that it's a friend function and for the Nth field of a POD type exactly one field_type(flag<N>) will be defined by the compiler.

g++ gives me no matching function for call to 'field_type(flag<1>) for decltype(field_type(flag<1>)).

I need to somehow force ADL to search in all instances of writer<T,N>. How can I do that?

Update

As @T.C.mentioned ADL only looks into associated classes, and writer isn't one. (That's why adl_flag is declared in flag - so that ADL could find it.)

How can I make writer an associated class without knowing the T value so that ADL can find it?

Galway answered 12/10, 2016 at 21:21 Comment(10)
Considering that the committee is going to ban the whole counter thing, I don't see the point in spending time on this.Watersoak
In any event, ADL (and this has nothing to do with ODR) only looks into associated classes, and writer isn't one. (That's why adl_flag is declared in flag - so that ADL could find it.)Watersoak
@Watersoak the committee is going to ban the whole counter thing Can please give a reference that indicate that's their intention? The ADL rules that meta-counter uses are old. How committee is going to change the rules without possibly breaking millions of code lines!Galway
@Watersoak Fixed the ODR mistake in the last line of question, i meant ADL :-)Galway
CWG issue 2118.Watersoak
They can't change the ADL rules, but the counter doesn't just depend on ADL.Watersoak
@Galway If you give me a complete example in a web compiler like coliru, I could check what's possible. It would be easier to test things with a code that reproduce the error from the start.Boastful
"YES! it works!!!" not in any compiler I've tried. The noexcept stuff you added differs from the original post.Jonette
@Jonette That's great news :-) I'm so exited to see your solution.Galway
@Jonette That version with noexcept is actually the "Workaround for VC++", listed at the end of the linked page. Try it with the Visual C++ online compiler, and it works. Basically, it compensates for MSVC's spotty implementation of: SFINAE, aggregate initialisation in constant expressions, and re-evaluation of expressions used as both template parameters & function parameter default values in the same function.Shipway
U
3

Add field_type declaration to flag template, with return type auto (only available after C++14)

Only works in gcc 4.9:

#include <type_traits>

template<int N>
struct flag {
    friend constexpr int adl_flag (flag<N>);
    friend constexpr auto field_type(flag<N>);
};

template<typename T, int N>
struct writer {
    friend constexpr int adl_flag (flag<N>) { return N; }
    friend constexpr auto field_type(flag<N>) { return (T&&)(*(T*)0); }  // remove default constructable restriction
    static constexpr int value = N;
};

template<int N, class = char[noexcept(adl_flag(flag<N> ()))?+1:-1]>
int constexpr reader (int, flag<N>) { return N; }

template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1>())) { return R; }

int constexpr reader (float, flag<0>) { return 0; }

template<typename T, int N = 1, int C = reader (0, flag<32> ())>
int constexpr next (int R = writer<T, C + N>::value) { return R; }

int main () {
    constexpr int a = next<int> ();
    constexpr int b = next<double> ();
    constexpr int c = next<long> ();

    // YES! it works!!!
    static_assert (a == 1 && b == a+1 && c == b+1, "try again");
    static_assert(std::is_same<decltype(field_type(flag<1>{})), int>{}, "first is int");
    static_assert(std::is_same<decltype(field_type(flag<2>{})), double>{}, "second is double");
    static_assert(std::is_same<decltype(field_type(flag<3>{})), long>{}, "third is long");

}

Use decltype of auto functions instead of noexcept, works after gcc 5.2, clang 3.5.1 - 3.7.1:

#include <type_traits>

template <int N>
struct flag {
    constexpr friend auto adl_flag(flag<N>);
    friend auto field_type(flag<N>);
};

template<typename T, int N>
struct writer {
    friend constexpr auto adl_flag(flag<N>) { return 0; }
    friend auto field_type(flag<N>) { return (T&&)(*(T*)0); }
    static constexpr int value = N;
};

template<int N, class = decltype(adl_flag(flag<N>{}))>
int constexpr reader (int, flag<N>) { return N; }

template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1>())) { return R; }

int constexpr reader (float, flag<0>) { return 0; }

template<typename T, int N = 1, int C = reader (0, flag<32> ())>
int constexpr next (int R = writer<T, C + N>::value) { return R; }

int main () {
    constexpr int a = next<int> ();
    constexpr int b = next<double> ();
    constexpr int c = next<long> ();

    // YES! it works!!!
    static_assert (a == 1 && b == a+1 && c == b+1, "try again");
    static_assert(std::is_same<decltype(field_type(flag<1>{})), int>{}, "first is int");
    static_assert(std::is_same<decltype(field_type(flag<2>{})), double>{}, "second is double");
    static_assert(std::is_same<decltype(field_type(flag<3>{})), long>{}, "third is long");

}
Umbrella answered 22/10, 2016 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.