Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointer to (data) member as non-type template parameter for instance with automatic storage duration / without linkage

Consider the following snippet:

#include <cstdint>
#include <iostream>

struct Foo {
    Foo() : foo_(0U), bar_(0U) {}

    void increaseFoo() { increaseCounter<&Foo::foo_>(); }
    void increaseBar() { increaseCounter<&Foo::bar_>(); }

    template <uint8_t Foo::*counter>
    void increaseCounter() { ++(this->*counter); }

    uint8_t foo_;
    uint8_t bar_;
};

void callMeWhenever() {
    Foo f;  // automatic storage duration, no linkage.
    f.increaseFoo();
    f.increaseFoo();
    f.increaseBar();

    std::cout << +f.foo_ << " " << +f.bar_;  // 2 1
}

int main() {
    callMeWhenever();
}

My first guess would've been that this was ill-formed, as f in callMeWhenever() has automatic storage duration, and its address is not known at compile time, whereas the member template function increaseCounter() of Foo is instantiated with pointers to data members of Foo, and the memory representation of a given class type is compiler specific (e.g. padding). However, from cppreference / Template parameters and template arguments, afaics, this is well-formed:

Template non-type arguments

The following limitations apply when instantiating templates that have non-type template parameters:

[..]

[until C++17] For pointers to members, the argument has to be a pointer to member expressed as &Class::Member or a constant expression that evaluates to null pointer or std::nullptr_tvalue.

[..]

[since C++17] The only exceptions are that non-type template parameters of reference or pointer type [added since C++20: and non-static data members of reference or pointer type in a non-type template parameter of class type and its subobjects (since C++20)] cannot refer to/be the address of

  • a subobject (including non-static class member, base subobject, or array element);
  • a temporary object (including one created during reference initialization);
  • a string literal;
  • the result of typeid;
  • or the predefined variable __func__.

How does this work? Is the compiler (by direct or indirect, e.g. the above, standard requirements) required to sort this out by itself, storing only (compile-time) address offsets between the members, rather than actual addresses?

I.e./e.g., is the compile time pointer to the data member non-type template argument counter in Foo::increaseCounter() (for each of the two specific pointer to data member instantations) simply a compile time address offset for any given instantiation of Foo, that will later become a fully resolved address for each instance of Foo, even for yet to be allocated ones such as f in the block scope of callMeWhenever()?

like image 914
dfrib Avatar asked Jan 09 '20 13:01

dfrib


1 Answers

Is the compiler (by direct or indirect, e.g. the above, standard requirements) required to sort this out by itself, storing only (compile-time) address offsets between the members, rather than actual addresses?

Pretty much. It's an "offset" even outside of a compile time context. Pointers to members are not like regular pointers. They designate members, not objects. That also means that the verbiage about valid pointer targets does not relate to pointer-to-members.

That's why to produce an actual lvalue out of them, one must complete the picture with something that refers to an object, such as we do in this->*counter. If you were to try and use this->*counter where a constant expression is required, the compiler would have complained, but it would have been about this, and not counter.

Their distinct nature is what allows them to uncondionally be compile time constants. There's no object that the compiler must check as a valid target.

like image 89
StoryTeller - Unslander Monica Avatar answered Oct 01 '22 02:10

StoryTeller - Unslander Monica