Is there automatic name lookup for enumerators in c++? - c++

As for as I know, currently in c++ there is no support for this.
For example,
class C{
struct{
enum {defaulted, opt1, opt2, ...} flag1;
enum {defaulted, optA, optB, ...} flag2;
} flags;
...
};
Now suppose we have C obj, to use the flag one would do
obj.flags.flag1 = obj.flags.opt1;
which is unnecessarily verbose.
However as the type of obj.flags.flag1 is known, in theory the compiler could lookup the name opt1 in that scope and save some typing.
Because the flags are not used anywhere else, it is preferred not to give names to the types (in fact it is difficult to come up with appropriate names). using enum syntax in c++20 does not solve this, because: it 1) requires the enum types to be named; 2) using multiple enums may lead to name collision, as shown here, since both enums have a defaulted member.
What will be the difficulties to implement this? Has this been proposed to the C++ standard committee and/or implemented in some compiler?

There could easily be an object named opt1 that could be converted to the appropriate enumeration type. It's better for name lookup to behave consistently rather than make special cases based on the types of other operands (partly because types are usually determined after name lookup).

Related

How can one implement is_aggregate without compiler magic? [duplicate]

C++11 provides standard <type_traits>.
Which of them are impossible to implement without compiler hooks?
Note 1: by compiler hook I mean any non-standard language feature such as __is_builtin....
Note 2: a lot of them can be implemented without hooks (see chapter 2 of C++ Template Metaprogramming and/or chapter 2 of Modern C++ Design).
Note 3: spraff answer in this previous question cites N2984 where some type traits contain the following note: is believed to require compiler support (thanks sehe).
I have written up a complete answer here — it's a work in progress, so I'm giving the authoritative hyperlink even though I'm cutting-and-pasting the text into this answer.
Also see libc++'s documentation on Type traits intrinsic design.
is_union
is_union queries an attribute of the class that isn't exposed through any other means;
in C++, anything you can do with a class or struct, you can also do with a union. This
includes inheriting and taking member pointers.
is_aggregate, is_literal_type, is_pod, is_standard_layout, has_virtual_destructor
These traits query attributes of the class that aren't exposed through any other means.
Essentially, a struct or class is a "black box"; the C++ language gives us no way to
crack it open and examine its data members to find out if they're all POD types, or if
any of them are private, or if the class has any constexpr constructors (the key
requirement for is_literal_type).
is_abstract
is_abstract is an interesting case. The defining characteristic of an abstract
class type is that you cannot get a value of that type; so for example it is
ill-formed to define a function whose parameter or return type is abstract, and
it is ill-formed to create an array type whose element type is abstract.
(Oddly, if T is abstract, then SFINAE will apply to T[] but not to T(). That
is, it is acceptable to create the type of a function with an abstract return type;
it is ill-formed to define an entity of such a function type.)
So we can get very close to a correct implementation of is_abstract using
this SFINAE approach:
template<class T, class> struct is_abstract_impl : true_type {};
template<class T> struct is_abstract_impl<T, void_t<T[]>> : false_type {};
template<class T> struct is_abstract : is_abstract_impl<remove_cv_t<T>, void> {};
However, there is a flaw! If T is itself a template class, such as vector<T>
or basic_ostream<char>, then merely forming the type T[] is acceptable; in
an unevaluated context this will not cause the compiler to go instantiate the
body of T, and therefore the compiler will not detect the ill-formedness of
the array type T[]. So the SFINAE will not happen in that case, and we'll
give the wrong answer for is_abstract<basic_ostream<char>>.
This quirk of template instantiation in unevaluated contexts is the sole reason
that modern compilers provide __is_abstract(T).
is_final
is_final queries an attribute of the class that isn't exposed through any other means.
Specifically, the base-specifier-list of a derived class is not a SFINAE context; we can't
exploit enable_if_t to ask "can I create a class derived from T?" because if we
cannot create such a class, it'll be a hard error.
is_empty
is_empty is an interesting case. We can't just ask whether sizeof (T) == 0 because
in C++ no type is ever allowed to have size 0; even an empty class has sizeof (T) == 1.
"Emptiness" is important enough to merit a type trait, though, because of the Empty Base
Optimization: all sufficiently modern compilers will lay out the two classes
struct Derived : public T { int x; };
struct Underived { int x; };
identically; that is, they will not lay out any space in Derived for the empty
T subobject. This suggests a way we could test for "emptiness" in C++03, at least
on all sufficiently modern compilers: just define the two classes above and ask
whether sizeof (Derived) == sizeof (Underived). Unfortunately, as of C++11, this
trick no longer works, because T might be final, and the "final-ness" of a class
type is not exposed by any other means! So compiler vendors who implement final
must also expose something like __is_empty(T) for the benefit of the standard library.
is_enum
is_enum is another interesting case. Technically, we could implement this type trait
by the observation that if our type T is not a fundamental type, an array type,
a pointer type, a reference type, a member pointer, a class or union, or a function
type, then by process of elimination it must be an enum type. However, this deductive
reasoning breaks down if the compiler happens to support any other types not falling
into the above categories. For this reason, modern compilers expose __is_enum(T).
A common example of a supported type not falling into any of the above categories
would be __int128_t. libc++ actually detects the presence of __int128_t and includes
it in the category of "integral types" (which makes it a "fundamental type" in the above
categorization), but our simple implementation does not.
Another example would be vector int, on compilers supporting Altivec vector extensions;
this type is more obviously "not integral" but also "not anything else either", and most
certainly not an enum type!
is_trivially_constructible, is_trivially_assignable
The triviality of construction, assignment, and destruction are all attributes of the
class that aren't exposed through any other means. Notice that with this foundation
we don't need any additional magic to query the triviality of default construction,
copy construction, move assignment, and so on. Instead,
is_trivially_copy_constructible<T> is implemented in terms of
is_trivially_constructible<T, const T&>, and so on.
is_trivially_destructible
For historical reasons, the name of this compiler builtin is not __is_trivially_destructible(T)
but rather __has_trivial_destructor(T). Furthermore, it turns out that the builtin
evaluates to true even for a class type with a deleted destructor! So we first need
to check that the type is destructible; and then, if it is, we can ask the magic builtin
whether that destructor is indeed trivial.
underlying_type
The underlying type of an enum isn't exposed through any other means. You can get close
by taking sizeof(T) and comparing it to the sizes of all known types, and by asking
for the signedness of the underlying type via T(-1) < T(0); but that approach still
cannot distinguish between underlying types int and long on platforms where those
types have the same width (nor between long and long long on platforms where those
types have the same width).
Per lastest boost documentation which is also a bit old but I think it's valid still
Support for Compiler Intrinsics
There are some traits that can not be implemented within the current C++ language: to make these traits "just work" with user defined types, some kind of additional help from the compiler is required. Currently (April 2008) Visual C++ 8 and 9, GNU GCC 4.3 and MWCW 9 provide at least some of the the necessary intrinsics, and other compilers will no doubt follow in due course.
The Following traits classes always need compiler support to do the right thing for all types (but all have safe fallback positions if this support is unavailable):
is_union
is_pod
has_trivial_constructor
has_trivial_copy
has_trivial_move_constructor
has_trivial_assign
has_trivial_move_assign
has_trivial_destructor
has_nothrow_constructor
has_nothrow_copy
has_nothrow_assign
has_virtual_destructor
The following traits classes can't be portably implemented in the C++ language, although in practice, the implementations do in fact do the right thing on all the compilers we know about:
is_empty
is_polymorphic
The following traits classes are dependent on one or more of the above:
is_class

What does vk::DeviceQueueCreateFlags() actually do?

The C like way to initialize this structure is:
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
The C++ way, using and abusing the vulkan.hpp header, is:
vk::DeviceQueueCreateInfo deviceQueueCreateInfo(vk::DeviceQueueCreateFlags(), static_cast<uint32_t>(graphicsQueueFamilyIndex), 1, &queuePriority);
It seems that a lot of work is encapsulated in the function vk::DeviceQueueCreateFlags().
However looking through the source with my editor is not revealing anything useful. I was hoping someone with more experience could provide some information as to what the function is doing.
It's an alias for vk::Flags, which is a template for dealing with Vulkan bitfields in a type-safe manor.
You want enumerations to be type-safe; you don't want implicit conversions to/from integers. So in C++, you do this by defining the enumeration to be an enum class.
That's fine for regular enumerations. But bitfields are special; they're combinations of enumerators from a specific enumeration. The typical post-C++11 solution is to just give the enumeration type overloaded operators, so that you can apply & and | to the enumeration type itself.
Personally, this solution always rubbed me the wrong way. To me, a strong enumeration type should hold one of the enum values, not several of them. So using operator overloading to effectively allow an enumeration value to lie didn't sit well with me.
Apparently I'm not alone in that, because the writers of vulkan.hpp opted for a different solution. They created a template class vk::Flags, which takes two template parameters. One of them is the enumeration that contains all of the valid bitflags. The second parameter is the "enumeration" that represents the concatenation of multiple flags. Which is something that the vulkan.xml specification file actually understands: the difference between the field containing the possible bits and the field which is an aggregation of bits.
vk::Flags can be given bits from the bitfield, and you can manipulate it with bits from that bitfield via some of the bitwise operators. And it is implicitly convertible to the type that represents the aggregation of bits.
So DeviceQueueCreateInfo is just an alias for Flags<DeviceQueueCreateFlagBits, VkDeviceQueueCreateFlags>, which is a bitfield that takes device creation bits and aggregates them into the flags aggregate.

Using aliases for strictly typing function parameters

It's possible to use an alias to change the literal signature of a function:
using String = std::string;
void Print(String s) { ... };
But this doesn't disallow calling Print with a std::string:
Print(std::string{"Hello world"}); // Still works
This makes sense -- an alias is strictly for simplifying a name for a type, it does not define a new type.
Besides subclassing, which is not a good idea, is there a mechanism by which it's possible to achieve strict typing by name for function parameters? An immediate consequence of this is that this would be possible as well:
using StringA = std::string;
using StringB = std::string;
void Print(StringA s) { ... };
void Print(StringB s) { ... };
Print(StringA{"Hello world"});
Print(StringB{"Hi everyone"});
My current solution is to define simple wrapper classes that hold the type I want to alias as a member. This isn't ideal because it requires duplicating the interface for the member class into the wrapper, something that isn't necessary when using an alias.
You are taking the right approach. Wrapping an object of the original type is the best that you can currently do.
I recently talked about this topic at CppCon 2015, and have open-sourced a library that makes it convenient to make an "opaque typedef" for integer types:
https://sourceforge.net/projects/opaque-typedef/
Be aware that it can be beneficial to customize the interface of your new type, to remove operations that would be errors for your purposes, and to add deliberate interoperability with other types. Slides from my presentation.
The standard makes typedefs and alias a synonym for another type and not a new type:
7.1.3/1: A name declared with the typedef specifier becomes a typedef-name. Within the scope of its declaration, a typedef-name is
syntactically equivalent to a keyword and names the type associated
with the identifier in the way described in Clause 8. A typedef-name
is thus a synonym for another type. A typedef-name does not introduce
a new type the way a class declaration or enum declaration does.
and
7.1.3/2: A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a
typedef-name. It has the same semantics as if it were introduced by
the typedef specifier. In particular, it does not define a new type and
it shall not appear in the type-id.
So unfortunately, you'll have to continue to use your class wrapper to introduce different types and being able to overload function on this base.
No, there is no way to get a new type without actually creating a new type (class, struct, enum).
However, there is a brand new proposal to add such a mechanism, perhaps using newtype as a keyword instead of typedef.

C++ type suffix _t, _type or none

C++ sometimes uses the suffix _type on type definitions (e.g. std::vector<T>::value_type),
also sometimes _t (e.g. std::size_t), or no suffix (normal classes, and also typedefs like std::string which is really std::basic_string<...>)
Are there any good conventions on when to use which name?
As #MarcoA.'s answer correctly points out, the suffix _t is largely inherited from C (and in the global namespace - reserved for POSIX).
This leaves us with "no suffix" and _type.
Notice that there is no namespace-scope name in std ending in _type*; all such names are members of classes and class templates (or, in the case of regex-related types, of a nested namespace which largely plays a role of a class). I think that's the distinction: types themselves don't use the _type suffix.
The suffix _type is only used on members which denote types, and moreover, usually when they denote a type somewhat "external" to the containing class. Compare std::vector<T>::value_type and std::vector<T>::size_type, which come from the vector's template parameters T and Allocator, respectively, against std::vector<T>::iterator, which is "intrinsic" to the vector class template.
* Not entirely true, there are a few such names (also pointed out in a comment by #jrok): common_type, underlying_type, is_literal_type, true_type, false_type. In the first three, _type is not really a suffix, it's an actual part of the name (e.g. a metafunction to give the common type or the underlying type). With true_type and false_type, it is indeed a suffix (since true and false are reserved words). I would say it's a type which represents a true/false value in the type-based metaprogramming sense.
As a C heritage the _t (that used to mean "defined via typedef") syntax has been inherited (they're also SUS/POSIX-reserved in the global namespace).
Types added in C++ and not present in the original C language (e.g. size_type) don't need to be shortened.
Keep in mind that to the best of my knowledge this is more of an observation on an established convention rather than a general rule.
Member types are called type or something_type in the C++ standard library. This is readable and descriptive, and the added verbosity is not usually a problem because users don't normally spell out those type names: most of them are used in function signatures, then auto takes care of member function return types, and in C++14 the _t type aliases take care of type trait static type members.
That leads to the second point: Free-standing, non-member types are usually called something_t: size_t, int64_t, decay_t, etc. There is certainly an element of heritage from C in there, but the convention is maintained in the continuing evolution of C++. Presumably, succinctness is still a useful quality here, since those types are expected to be spelled out in general.
Finally, all the above only applies to what I might call "generic type derivation": Given X, give me some related type X::value_type, or given an integer, give me the 64-bit variant. The convention is thus restricted to common, vocabulary-type names. The class names of your actual business logic (including std::string) presumably do not warrant such a naming pattern, and I don't think many people would like to have to mangle every type name.
If you will, the _t and _type naming conventions apply primarily to the standard library and to certain aspects of the standard library style, but you do not need to take them as some kind of general mandate.
My answer is only relevant for type names within namespaces (that aren't std).
Use no suffix usually, and _type for enums
So, here's the thing: the identifier foo_type can be interpreted as
"the identifier of the type for things which are foo's" (e.g. size_type overall_size = v1.size() + v2.size();)
"the identifier of the type for things which are kinds, or types, of foo" (e.g. employment_type my_employment_type = FIXED_TERM;)
When you have typedef'ed enums in play, I think you would tend towards the second interpretation - otherwise, what would you call your enum types?
The common aversion to using no suffix is that seeing the identifier foo is confusing: Is it a variable, a specific foo? Or is it the type for foos? ... luckily, that's not an issue when you're in a namespace: my_ns::foo is obviously a type - you can't get it wrong (assuming you don't use global variables...); so no need for a prefix there.
PS - I employ the practice of suffixing my typedef's within classes with _type (pointer_type, value_type, reference_type etc.) I know that contradicts my advice above, but I somehow feel bad breaking with tradition on this point.
Now, you could ask - what happens if you have enums within classes? Well, I try to avoid those, and place my enum inside the surrounding namespace.

enum [tag] [: type] {enum-list} [declarator] in C++ - is this legal?

I am working with a custom enumerated type in C++, but it does not have many values. I want to try to reduce the size that they take up, and I've heard that enum types are always integers by default. I then came across the MSDN entry on C++ enumerations, and found the following syntax very interesting:
enum [: type] {enum-list};
Sure enough, it compiled with what I wanted (VS2008) when I did the following:
enum plane : unsigned char { xy, xz, yz };
Now, you can see from my enumeration constants that I don't need much in terms of space - an unsigned char type would be perfect for my uses.
However, I have to say, I've never seen this form used anywhere else on the internet - most don't even seem aware of it. I'm trying to make this code cross-platform (and possibly for use on embedded systems), so it left me wondering... Is this proper C++ syntax, or only supported by the MSVC compiler?
Edit: It seems that this feature is now part of C++11 and above, and is called scoped enumerations.
This is non-standard, but it is expected to be a part of the C++0x standard. For me, when I compile in Visual Studio 2005 with warning level set to maximum I get the following warning:
warning C4480: nonstandard extension used: specifying underlying type for enum 'Test'
From the Wikipedia page for C++0x:
In standard C++, enumerations are not type-safe. They are effectively
integers, even when the enumeration types are distinct. This allows
the comparison between two enum values of different enumeration types.
The only safety that C++03 provides is that an integer or a value of
one enum type does not convert implicitly to another enum type.
Additionally, the underlying integral type is implementation-defined;
code that depends on the size of the enumeration is therefore
non-portable. Lastly, enumeration values are scoped to the enclosing
scope. Thus, it is not possible for two separate enumerations to have
matching member names.
Additionally, C++0x will allow standard enumerations to provide
explicit scoping as well as the definition of the underlying type:
enum Enum3 : unsigned long {Val1 = 1, Val2};
As 0A0D's said, the notation you're using is non-Standard in C++03, but has been adopted by C++11 under the term "scoped enums".
If that's not soon enough for you, then you can consider explicitly specifying the bit field width used for the enum fields in the size-critical structures in which they're embedded. This approach is ugly - I mention if for completeness; if it was a good solution the notation above wouldn't be being adopted for C++11. One problem is that you rely on an optional compiler warning to detect too-small bit-fields to hold the possible values, and may have to manually review them as the enumeration values change.
For example:
enum E
{
A, B, C
};
struct X
{
E e1 : 2;
E e2 : 2;
E e3 : 2;
E e4 : 2;
};
Note: the enum may occupy more bits than requested - on GCC 4.5.2 with no explicit compiler options, sizeof(X) above is still 4....
It's perfectly legal. Embedding the type in an object to save space however will not be effective without specifically ensuring the alignment. If you leave default alignment, padding bytes will be added (depending on the architecture) usually to a quad-word boundary.