1

I have the following code (as is):

template<class T, class FieldT>
using addRefU = typename std::conditional<
                            std::is_rvalue_reference<T>::value,
                            typename std::add_rvalue_reference< FieldT >::type,
                            typename std::conditional<
                                std::is_rvalue_reference<FieldT>::value,
                                typename std::add_rvalue_reference< FieldT >::type,
                                typename std::add_lvalue_reference< FieldT >::type
                            >::type
                        >::type;

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 4, 3, 2, 1, 0)
#define VARARG_IMPL2(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL2(base, count, __VA_ARGS__)
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)


#define REF2(val, p1) addRefU<decltype(val), decltype(val.p1)>
#define REF3(val, p1, p2) addRefU<decltype(val), decltype(val.p1.p2)>
#define REF4(val, p1, p2, p3) addRefU<decltype(val), decltype(val.p1.p2.p3)>
#define REF5(val, p1, p2, p3, p4) addRefU<decltype(val), decltype(val.p1.p2.p3.p4)>
#define REF(...) VARARG(REF, __VA_ARGS__)     // It says REF is not defined here



#define CAST_REF2(val, p1) static_cast<REF(val, p1)>(val.p1)
#define CAST_REF3(val, p1, p2) static_cast<REF(val, p1, p2)>(val.p1.p2)
#define CAST_REF4(val, p1, p2, p3) static_cast<REF(val, p1, p2, p3)>(val.p1.p2.p3)
#define CAST_REF5(val, p1, p2, p3, p4) static_cast<REF(val, p1, p2, p3, p4)>(val.p1.p2.p3.p4)
#define CAST_REF(...) VARARG(CAST_REF, __VA_ARGS__)


struct A{};
struct B{A a;};

int main()
{
    B b;

    using t = REF(b, a);    // Ok

    auto &&k = CAST_REF2(b, a); // work
    auto &&k1 = CAST_REF(b, a); // NOT work

    return 0;
}

http://coliru.stacked-crooked.com/a/8dff49f68b7e15e1

If I change

#define CAST_REF2(val, p1) static_cast<REF(val, p1)>(val.p1) to

#define CAST_REF2(val, p1) static_cast<REF2(val, p1)>(val.p1) (change REF to REF2)

It works. I don't understand why I can call REF directly, and can't do this from macro.

tower120
  • 5,007
  • 6
  • 40
  • 88
  • Your "// work", "// NOT work" comments are backwards :) – Lightness Races in Orbit Jun 25 '14 at 10:05
  • @LightnessRacesinOrbit Thanks, live example updated. – tower120 Jun 25 '14 at 10:13
  • Using preprocessor voodoo seems to be en vogue again, in certain circles. After seeing how std::function is done in #define style, I am not really amazed... Why not simply write: `auto k = b.a;` ? – BitTickler Jun 25 '14 at 10:15
  • This is pretty much the same as http://stackoverflow.com/q/5641836/560648, I think. – Lightness Races in Orbit Jun 25 '14 at 10:16
  • @user2225104 I just realize this code a little bit broken for 3 and more params. But for 2 it works. It deduces rvalue/lvalue reference based on qualifier of each parameter in chain. Here described why this needed http://stackoverflow.com/a/24384906/1559666. You can't just get parameter from && value - it will loose qualfier http://stackoverflow.com/questions/24380677/are-objects-inside-rvalue-referenced-object-also-rvalue-referenced – tower120 Jun 25 '14 at 10:59

1 Answers1

3

Your code (sans includes) preprocesses to the following:

struct A{};
struct B{A a;};

int main()
{
    B b;

    using t = addRefU<decltype(b), decltype(b.a)>

    auto &&k = static_cast<VARARG(REF, b, a)>(b.a);
    auto &&k1 = static_cast<addRefU<decltype(b), decltype(b.a)> >(b.a);

    return 0;
}

Clearly, VARARG hasn't been expanded, and this is because it was already expanded previously in the expansion of CAST_REF (which isn't the case for CAST_REF2):

[C++11: 16.3.4/2]: If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

The intent of the rule is to prohibit infinite recursions, even though you're not actually at risk of one in this particular case.

This has been discussed on Stack Overflow before, but I'm not entirely sure how you can get around it, at this point (other than by copying VARARG).

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055