geometrian
geometrian

Reputation: 15407

Creating effect of method variable static both per-instantiation and per-enclosing-instance

It is well-known that static variables of a routine, function, or method ("member function") exist for each unique instantiation.


(Quick review and sanity check.)

In the common case, this is exactly one variable:

int f() {
    static int i = 0;
    return i++;
}

That is, there is a single variable i that is created in .BSS/.DATA that "belongs" to the function f. For templates, it's one per unique instantiation:

template <typename T> int f() {
    static int i = 0;
    return i++;
}
int g() {
    return f<int>() + f<int>() + f<int>() + f<float>();
}

Here, there are two unique instantiations (f<int> and f<float>), and so there are two is in the .BSS/.DATA segment.


Problem

I want some way to make variables in template member functions live both per-instance and per-instantiation of their enclosing class. I am interested in achieving this EFFECT, by more-or-less any performant means necessary (possibly not involving static at all). How should I do this?

For example:

class Foo { public:
    template <typename T> int f() {
        static int i = 0;
        return i++;
    }
};

void g() {
    Foo foo1;
    Foo foo2;
    /*
        These will have values a=0, b=0, c=1.  This happens because there are two
        variables:
            "Foo::f<float>()::i"
            "Foo::f<double>()::i"
        What I *want* is for there to be three variables:
            "Foo::f<float>()::i" (foo1's copy)
            "Foo::f<float>()::i" (foo2's copy)
            "Foo::f<double>()::i" (foo1's copy)
        So the result is a=0, b=0, c=0.  How can I accomplish this effect (maybe
        not using static)?
    */
    int a = foo1.f<float>();
    int b = foo1.f<double>();
    int c = foo2.f<float>();
}

Upvotes: 2

Views: 98

Answers (3)

W.F.
W.F.

Reputation: 13988

As in general one should avoid using typeid an alternative might be variable template. Of course variable template may only be static so to store unique value for each instance one need variable template mapping instance -> value.

#include <cassert>
#include <map>

struct Foo {
    template <class T> static std::map<Foo *, int> m;

    template <class T> int f() {
        return m<T>[this]++;
    }
};

template <class T> std::map<Foo *, int> Foo::m;


int main() {
    Foo foo1;
    Foo foo2;
    assert(foo1.f<int>() == 0);
    assert(foo1.f<int>() == 1);
    assert(foo1.f<int>() == 2);
    assert(foo1.f<float>() == 0);
    assert(foo2.f<int>() == 0);
    assert(foo2.f<int>() == 1);
    assert(foo2.f<int>() == 2);
    assert(foo2.f<float>() == 0);
}

[live demo]

It might also be a good idea to replace std::map with std::unordered_map (it will not require any additional hasher - example)

Approach can also be successfully applied before c++14 as a variable template can be simply replaced with inner structure template.

Warning
It have to be stated that by default an approach won't support deep copy cloning value for given instance.


To overcome the problem one can use a little bit modified technique still utilizing variable templates and mapping (still no RTTI required):

#include <cassert>
#include <unordered_map>

struct Foo {
    template <class T>
    static int label;

    std::unordered_map<int *, int> m;

    template <class T> int f() {
        return m[&label<T>]++;
    }
};

template <class T> int Foo::label;

int main() {
    Foo foo1;
    Foo foo2;
    assert(foo1.f<int>() == 0);
    assert(foo1.f<int>() == 1);
    assert(foo1.f<int>() == 2);
    assert(foo1.f<float>() == 0);
    assert(foo2.f<int>() == 0);
    assert(foo2.f<int>() == 1);
    assert(foo2.f<int>() == 2);
    assert(foo2.f<float>() == 0);
}

[live demo]

Upvotes: 1

kennytm
kennytm

Reputation: 523794

You could store a type map for each instance. Here I assume RTTI is enabled to use type_index, if you cannot use RTTI, check out Boost.TypeIndex or Template metaprogram converting type to unique number for its replacement.

#include <unordered_map>
#include <typeindex>
#include <cstdio>

class Foo {
    std::unordered_map<std::type_index, int> _values;

public:
    template<typename T>
    int f() {
        int& i = _values[typeid(T)];
        return i++;
    }
};

int main() {
    Foo foo1;
    Foo foo2;

    int a = foo1.f<float>();
    int b = foo1.f<double>();
    int c = foo2.f<float>();

    foo1.f<float>();

    int d = foo1.f<float>();
    int e = foo1.f<double>();
    int f = foo2.f<float>();

    printf("%d %d %d %d %d %d\n", a, b, c, d, e, f);
    // prints 0 0 0 2 1 1
}

Upvotes: 2

Barry
Barry

Reputation: 304182

The only way to have different instances of an object to give you different results is to have those objects have member variables with different values. The simplest approach is just to have a std::map of std::type_info:

struct TypeInfoCompare {
    bool operator()(std::type_info const* lhs, std::type_info const* rhs) const {
        return lhs->before(*rhs);
    }
};

struct Foo {
    template <class T>
    int f() {
        return m[&typeid(T)]++;
    }

    std::map<std::type_info const*, int, TypeInfoCompare> m;
};

This gives you a per-type counter that will be different for each instance of Foo.


The std::map could also be a std::unordered_map, and use std::type_info::hash_code() as the hash.

Upvotes: 3

Related Questions