3

Suppose I have a template class, which depending on a template parameter, may or may not have a member int x. This can be realized by inheriting from a base template class, which for some specialization has a member int x.

Example code:

#include <iostream>

template <bool present>
struct base;

template <>
struct base<true> { int x; };

template <bool present>
struct base { };

template <bool activate>
struct A : public base<activate> {
    void print() const;
};

template <bool activate>
void A<activate>::print() const
{
    if constexpr (activate) {
        std::cout << "x = " << this->x << std::endl;
    } else {
        std::cout << "nothing" << std::endl;
    }
}

int main()
{
    A<true> a;
    a.print();
    A<false> b;
    b.print();

    return 0;
}

In the code above A<true> contains a member int x, inherited from base<true>, whereas A<false> does not contain it.

Now, since x is a dependent name, in order to access it, I need to use this->x or base<true>::x. This can be somewhat burdersome to use it every time, so the common solution is to employ a using directive like

using base<true>::x;

inside the definition of A. But, of course, this makes sense only when activate=true.

Is it possible, perhaps with a macro, to add using base<true>::x in the definition of A, only when a condition (here activate=true) is satisfied?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
francesco
  • 7,189
  • 7
  • 22
  • 49
  • What compiler are you using? What version? I ask because this is trivial with concepts and template meta-programming but requires a new-ish compiler that supports at least late features of C++20 or early previews of C++23. – Casey Jan 03 '23 at 20:21
  • 1
    Hacking by adding dummy `typedef` ([Demo](https://godbolt.org/z/o4dPcqrnj)) or static var ([Demo](https://godbolt.org/z/z4oTd98Ge)) seems to pass with gcc/msvc, but not with clang. – Jarod42 Jan 03 '23 at 20:29
  • @Casey I use the c++17 standard, although the compilers I am typically using (g++ >=11), should partially support c++20 I think. – francesco Jan 03 '23 at 20:30
  • @Jarod42 your second option (with a ```static struct dummy``` member) seems to work with all compilers, including clang. – francesco Jan 03 '23 at 21:42
  • Indeed, I got a "refresh" issue on clang with static var. but passes indeed with all compiler. – Jarod42 Jan 03 '23 at 22:39
  • @Jarod42 Concerning the solution with a ```static struct dummy```. From [this answer](https://stackoverflow.com/a/1421780/8769985) I get that whether the code compiles or not is implementation-dependent: the initialization of the incomplete type ```static dummy x``` *can* be deferred before its first use (in which case if I don't use it at all, it is never initialized), but it could also be initialized before main (in which case the code would not compile). Is that right? Anyway it works with all compilers I may use. – francesco Jan 04 '23 at 09:12

3 Answers3

2

It's a common issue to have optionally present members. If you're okay with multiple inheritance and some hacking, you can do this via a static empty variable in another base class:

struct Nothing {};

struct StaticNothing {
    static Nothing x;   
};

template <bool activate>
struct A : public base<activate>, StaticNothing {
    using std::conditional_t<activate, base<true>, StaticNothing>::x;
    void print() const;
};

Demo

Note that std::conditional_t<> decides which base x comes from. The benefit of this method is, you can always assume that there's an x member (either static or non-static), so you can take the address of it, etc.

lorro
  • 10,687
  • 23
  • 36
  • imo [the option provide by @Jarod42 in comment](https://stackoverflow.com/questions/74998170/access-to-optionally-present-members-of-base-template-class/74998451#comment132349643_74998170) is better, the name is always presented in `base`, just defined differently https://godbolt.org/z/GKnGo6e98 – apple apple Jan 03 '23 at 21:22
  • @appleapple You can use that - as long as you can use that. Sometimes `base<>` is in a library / code part where you are not allowed to modify anything. `A` is assumed to be under implementation, therefore, by definition, modifiable (and `Nothing` / `StaticNothing` can reside anywhere). – lorro Jan 03 '23 at 22:15
  • @Iorro well there can always be another layer. – apple apple Jan 03 '23 at 23:23
1

base on the comment of @Jarod42, but use anonymous class (optional) and inline variable to make sure it compiles without extra definitions. (regarding to OP's this comment)

template <bool present>
struct base { constexpr static struct{} x = {}; };
template <>
struct base<true> { int x = 0; };

template <bool activate>
struct A : public base<activate> {
    using base<activate>::x;
    void print() const;
};
apple apple
  • 10,292
  • 2
  • 16
  • 36
1

another placeholder option is use a function, with the benefit that one can delete it to prevent unwanted access.

template <bool present>
struct base { constexpr void x()=delete; };

template <>
struct base<true> { int x = 0; };

template <bool activate>
struct A : public base<activate> {
    using base<activate>::x;
    void print() const{
        auto&& p = x; // fail for A<false>::print
    }
};
apple apple
  • 10,292
  • 2
  • 16
  • 36