I have a few classes that define sequences whose values must be available both at compile-time through a value member and at runtime as an actual instance of the type. So my base types for an arithmetic sequence looks a little like this,
template<int A, int D>
struct ArithmeticSequence : public Sequence {
ArithmeticSequence(VALUE v)
: Sequence(v) {}
template<unsigned int N>
struct VALUE_N : public VALUE {
static const int value = A+(D*N);
operator int() { return value; }
};
};
class Sequence currently just defines an inner class VALUE (currently empty) and a constructor that takes a VALUE, but I will move the operator int() of VALUE_N into VALUE and Sequence will define iterators, etc, further down the line.
Now, classes should extend from ArithmeticSequence and define constants for each of the members of the sequence. I’ve got two methods that I think will work for this, if I don’t mind instances of sequences being able to be constructed from members of related sequences (that is, sequences with the same initial value and common difference), I can use typedef:
struct mySequence : public ArithmeticSequence<0,1> {
mySequence(VALUE val = VALUE_N<0>::value)
: ArithmeticSequence(val) {}
typedef VALUE_N<0> zeroth;
typedef VALUE_N<1> first;
// ...
};
And if I do, I can extend from VALUE_N:
struct mySequence : public ArithmeticSequence<0,1> {
mySequence(VALUE val = VALUE_N<0>::value)
: ArithmeticSequence(val) {}
struct zeroth : public VALUE_N<0> {};
struct first : public VALUE_N<1> {};
// ...
};
In both these cases I think I can use mySequence::zeroth::value to get at the value at compile time, and mySequence::zeroth() to get a runtime object. However using the second method causes the compiler confusion as to whether I’m declaring a function or initializing an instance, so I need, mySequence s1 ((mySequence::zeroth())); instead of mySequence s1 (mySequence::zeroth()).
Now, I’ve found that the following is valid,
struct mySequence : public ArithmeticSequence<0,1> {
mySequence(VALUE val = VALUE_N<0>::value)
: ArithmeticSequence(val) {}
struct zeroth : public VALUE_N<0> {};
static const zeroth zeroth;
struct first : public VALUE_N<1> {};
static const first first;
// ...
};
But my question (finally) is, what are the rules as to which one I’m accessing at any time? I can use static const int i = mySequence::zeroth::value and, mySequence s1 (mySequence::zeroth), so the right thing seems to happen there, but if I say mySequence::zeroth z instead of treating zeroth as a class it treats it as the variable. This isn’t a problem in this case since I don’t want people creating new mySequence::zeroth‘s or any other value, but I think if I don’t understand when it will use each one I may let myself in for trouble at a later date.
Sorry about the extra long post, and thankyou in advance for your time and patience for anyone who got this far. I’m wondering now if I should have put in all the back story or just simply asked the question, if the concensus is I should have, I’ll edit it down. Thanks.
Edit. Please note, as I have written it above, using the struct method as opposed to the typedef does not provide any protection against using another “related” sequences members to construct a sequence object, it is needed, I think, however for the last example to work.
The names of enumerators, function and objects hide the names of enumerations and classes that are declared in the same scope. In your case, the data member name hides the name of the struct. You can access the hidden type name by special lookups:
::is looked up by ignoring object-, function- and enumerator names.Thus, the following elaborated type specifier is valid and refers to the class
Also, note that it is ill-formed when in class scope a member declaration changes the meaning of a name used in that declaration. In your case, let’s take
static const first first;. The first name will refer to the type, but in the complete scope ofmySequence, that name would refer to the data member. The Standard saysYour compiler is not required to diagnose it, which is a phrase meaning that it is effectively undefined behavior (good compilers warn you with something like “member changes meaning of name”). Albeit i doubt that the above rule is intended to apply in this case (as it is worded, it certainly applies though), you can clear the code up by using an elaborated type specifier
Notice that you are required to use the elaborated type specifier in the out-of-class definition of the static member. Some compilers allow you to use the injected class name for refering to the type too (GCC did, in the past)
The following uses the injected class name.
firstappears before::and ignores the data member. But the compiler has to lookup the namemySequence::first::firsttofirst‘s constructor and not to its class type