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> {
mySeque开发者_Python百科nce(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:
- The name prior to
::
is looked up by ignoring object-, function- and enumerator names. - The name used to specify a base class ignores any non-type names.
- The name specified in an elaborated type specifier ignores object-, function- and enumerator names.
Thus, the following elaborated type specifier is valid and refers to the class
struct mySequence::zeroth var;
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 of mySequence
, that name would refer to the data member. The Standard says
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.
Your 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
struct first : public VALUE_N<1> { };
static const struct first first;
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)
const struct mySequence::first mySequence::first;
The following uses the injected class name. first
appears before ::
and ignores the data member. But the compiler has to lookup the name mySequence::first::first
to first
's constructor and not to its class type
const mySequence::first::first mySequence::first;
I have to admit that I'm not totally certain if this is your only issue, but I think that you are just coming up against the "most vexing parse" issue.
C++ grammar is ambiguous. Certain chunks of C++ can be parsed in more than one way. The classic form of the most vexing parse is:
A a(A());
The 'obvious' interpretation is that it is a definition of an object a
of type A
with an initializer of a default constructed A
, however the A()
could be parsed as a function declaration rather than an initializer. The C++ rules specify that if this ambiguity arises then a declaration should be interpreted in preference to an expression.
This means that this actually declares a
as a function taking a function (taking no parameters and returning an A
), and returning an A
.
One way to disambiguate is to use an extra pair of parentheses:
A a( (A()) );
In your template, I think that you've just come across this issue in a slightly more disguised form.
精彩评论