7

Is it possible to write a trait, which results the type of the class it is used in? How to implement get_class in the example below?

class Foo {
    typedef get_class::type type; // type = Foo now
}; 

Note: I have to write a macro, which expands in the class body, used for multiple classes, so I cannot simply write 'typedef Foo type;'

Use case:

I have a reflectable(...) macro which generates infrastructure to iterate over members, visit them and look them up using their names:

class Foo 
{
    friend std::ostream &operator<<(std::ostream &, const Foo&);
    reflectable(
         (int) bar,
         (double) baz
    )
}

reflectable(...) should be a macro so I can get the types and member names separately as strings to build maps for the lookup.

I would like all reflectable class to be streamable, but if I put my reflectable() macro to private section, I have to add the friend declaration to the class. I would like to move it to the macro as:

friend std::ostream &operator<<(std::ostream &, const get_class::type&);
simon
  • 1,210
  • 12
  • 26
  • What's your use-case for this? I don't see it. – DeiDei Jun 19 '17 at 10:37
  • Well, you can always accept enclosing type name as a macro parameter. Also some coding standards require each class to declare a class type alias member. – user7860670 Jun 19 '17 at 10:38
  • 1
    If it is acceptable in your case, you can write a helper template class which will `typedef` it's template parameter and you will neet to derive `Foo` from `Helper`. – k.v. Jun 19 '17 at 10:42
  • @DeiDei I have updated the question with my use case. – simon Jun 19 '17 at 10:49
  • @k.v. I don't know Foo in the macro, so I cannot instantiate the Helper class. – simon Jun 19 '17 at 10:53
  • @VTT Yes, both solution is possible, but it would need redundant code in the user class which I would like to avoid. – simon Jun 19 '17 at 10:55
  • Suggestion: use Boost.Fusion adapted sequences to iterate over members. It will save you a lot of headaches. – alfC Jun 19 '17 at 10:57
  • 1
    Possible duplicate of [How to refer current class using decltype in C++11?](https://stackoverflow.com/questions/20203640/how-to-refer-current-class-using-decltype-in-c11) – cpplearner Jun 19 '17 at 11:52
  • @cpplearner That question is a near-dupe, but there are not satisfying answers (no accepted answer too). Perhaps the OP should offer a bounty on that question, instead of asking again. – Walter Jun 19 '17 at 15:08
  • @cpplearner, yes, the question seems to be a duplicate first, but if you consider the use case - I have added, and - would like to solve, it is not an exact duplicate. – simon Jun 19 '17 at 15:23
  • Just have your macro befriend some class like `reflectable_access`, then that class can access the private stuff defined by your macro and expose it publicly. – T.C. Jun 19 '17 at 16:51
  • @T.C. Thanks! It solves my problem completely! – simon Jun 19 '17 at 18:23

4 Answers4

2

I'm sorry, but I'm pretty sure that in standard C++ there is no way around you having to pass in the class name to the macro one way or another. For some failed attempts, see the end of this answer.


That said, I think your best bet is to slightly change your design. Instead of

friend std::ostream &operator<<(std::ostream &, const get_class::type&);

provide a public member function

void stream_to (std::ostream &) const {
  // Implementation
}

together with a free function template

template<typename T, typename std:: enable_if<has_stream_to<T>::value>::type * = nullptr>
std::ostream &operator<<(std::ostream & s, T const & t) {
  t.stream_to(s);
  return s;
}

in your libraries/programs namespace. (Note: I'll add the trait has_stream_to later, for the impatient search for "detect member function" within C++ tag)


Approaches that don't work:

  • Use a pointer to member function, extract the class type with a template function. Reason: &foo inside of a class with a member function foo won't give a member function pointer to foo. The syntax is required (by the standard) to be &C::foo (where C is the class name...).

  • Use a pointer to data member, extract the class type with a template function. Same reason as above.

  • Use a dedicated member function that returns *this, deduce the return type to get the class type. Reason: Need an instance to call this function on.

  • Use a pointer to a static member function. Reason: Such pointers are same as pointers to free functions, the class name cannot be deduced from them.

  • Use the this pointer in a member initialisation. Reason: type of member needs to encode class type somehow, but auto is not allowed for non static data members.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • @skypjack Why did you delete your answer? – Daniel Jour Jun 19 '17 at 11:33
  • Compiles with G++ 5.4, but MSVC and clang complains: 'this' cannot be used in a static member function declaration – simon Jun 19 '17 at 11:36
  • I have been using a similar approach for some time now, and it works pretty fine (under G++). Only one note, both the `get_type()` function and the `type` declaration should be private, in case the OP is using inheritance with several classes deducing their type – LoPiTaL Jun 19 '17 at 11:40
  • Compiles fine though ... but this is most likely a gcc bug. – Daniel Jour Jun 19 '17 at 11:40
  • 1
    I deleted the answer for that code isn't valid. You should rather remove it from your answer. – skypjack Jun 19 '17 at 12:48
  • @skypjack I did. It indeed isn't valid. Any idea whether this is already submitted as bug report to GCC? – Daniel Jour Jun 19 '17 at 12:58
  • @DanielJour I've already seen it, so I'd say yes. Meanwhile I resumed my answer and added a slightly modified version that compiles and is also valid code. – skypjack Jun 19 '17 at 13:04
  • Dear down voter, please explain how I can improve my answer. – Daniel Jour Jun 19 '17 at 13:45
  • 2
    I didn't downvote, but note that your `stream_to` still has to be public for the detection to work. Also, that `final` is ill-formed on a non-virtual member function. – T.C. Jun 19 '17 at 16:44
0

Note: I have to write a macro, which expands in the class body, used for multiple classes, so I cannot simply write 'typedef Foo type;'

If you can expand the macro when declaring the class instead of in the class body, something like this could work:

#include<type_traits>

template<typename T>
struct Type {
    using type = T;
};

#define struct_with_type(S) struct S: Type<S>

struct_with_type(Foo) {};

int main() {
    static_assert(std::is_same<Foo::type, Foo>::value, "!");
}

Note that the example proposed with my previous answer isn't valid code:

struct Foo {
    static constexpr auto get_type() -> std::decay_t<decltype(*this)>;
    using type = decltype(get_type());
};

It compiles only with GCC because of a bug of that compiler.
Something I wouldn't use in a production code.

That being said, if you like it, the following compiles and it's something close to that solution:

#include<type_traits>
#include<utility>

#define add_type() constexpr auto type() -> std::decay_t<decltype(*this)>;

template<typename T>
using get_type = decltype(std::declval<T>().type());

struct Foo {
    add_type();
};

int main() {
    static_assert(std::is_same<get_type<Foo>, Foo>::value, "!");
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Thanks, unfortunately I cannot use your last code snippet, I would need get_type inside the macro, so I cannot instantiate it with Foo. On the other hand the struct_with_type macro might work, limits additional inheritence though. – simon Jun 19 '17 at 15:06
  • @simon You can easily extend the macro to support inheritance. – skypjack Jun 19 '17 at 15:12
0

It is not a really nice solution, but it might do the trick for you on simple functions. I have not extended it so far for templates and other stuff such as variadic functions:

#define FUNCTION_DECL(R, N, ...) \
        R N(__VA_ARGS__);

#define DEFINE_FRIEND_OPERATOR(CLASS_NAME, MEMBERS)                       \
    friend std::ostream& operator<<(std::ostream& s, CLASS_NAME const& f) \
    {                                                                     \
        std::cout << #CLASS_NAME << ':' << std::endl;                     \
        MEMBERS                                                           \
        return s;                                                         \
    }

#define S_(X) #X
#define S(X) S_(X)

#define STREAM_FUNCTION(R, N, ...) \
    s << "  " #R " " #N S((__VA_ARGS__)) << std::endl;

I do not see any portable way of having the class name other than passing it to the macro, thus the type trait skipped.

You could now place above macros into some appropriate header (and find better/more suitable names...), which would be included for each class. There, you'd have:

class Foo
{
#define FOO_MEMBERS                    \
        MEMBER(int, f0, int, int, int) \
        MEMBER(double, f1, double)     \
        MEMBER(void, f2, char const)

public:
#ifdef MEMBER
#undef MEMBER
#endif
#define MEMBER FUNCTION_DECL
    FOO_MEMBERS
#undef MEMBER

private:
#define MEMBER STREAM_FUNCTION
    DEFINE_FRIEND_OPERATOR(Foo, FOO_MEMBERS)
#undef MEMBER
};

For the map entries, you'd have similar macros, generating an initialiser list in the constructor such as shown here.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
0

As I mentioned in the comments, this sounds like an XY problem. Instead, add a layer of indirection:

class Foo 
{
    friend class reflectable_access;
    reflectable(
         (int) bar,
         (double) baz
    )
};

Now reflectable_access can access the things added by your reflectable macro and expose them to others requiring access to them.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • If it's a template class I can move the friend declaration to the macro and instantiate it where I want to use. – simon Jun 19 '17 at 19:31