Attributes are one of the key features of modern C++ which allows the programmer to specify additional information to the compiler to enforce constraints(conditions), optimise certain pieces of code or do some specific code generation. In simple terms, an attribute acts as an annotation or a note to the compiler which provides additional information about the code for optimization purposes and enforcing certain conditions on it. Introduced in C++11, they have remained one of the best features of C++ and are constantly being evolved with each new version of C++. Syntax:
// C++11 [[attribute-list]] // C++17 [[using attribute-namespace:attribute-list]] // Upcoming C++20 [[contract-attribute-token contract-level identifier : expression]] Except for some specific ones, most of the attributes can be applied with variables, functions, classes, structures etc.
Purpose of Attributes in C++
1. To enforce constraints on the code: Here constraint refers to a condition, that the arguments of a particular function must meet for its execution (precondition). In previous versions of C++, the code for specifying constraints was written in this mannerÂ
CPP
int f(int i)
{
if (i > 0)
return i;
else
return -1;
// Code
}
It increases the readability of your code and avoids the clutter that is written inside the function for argument checking.Â
CPP
int f(int i)[[expects:i > 0]]
{
// Code
}
2. To give additional information to the compiler for optimisation purposes: Compilers are very good at optimization but compared to humans they still lag at some places and propose generalized code which is not very efficient. This mainly happens due to the lack of additional information about the "problem" which humans have. To reduce this problem to an extent C++ standard has introduced some new attributes that allow specifying a little more to the compiler rather than the code statement itself. Once such example is that of likely.Â
CPP
int f(int i)
{
switch (i) {
case 1:
[[fallthrough]];
[[likely]] case 2 : return 1;
}
return -1;
}
When the statement is preceded by likely compiler makes special optimizations with respect to that statement which improves the overall performance of the code. Some examples of such attributes are [carries_dependency], [likely], [unlikely]
3. Suppressing certain warnings and errors that programmer intended to have in his code: It happens rarely but sometimes the programmer intentionally tries to write a faulty code which gets detected by the compiler and is reported as an error or a warning. One such example is that of an unused variable which has been left in that state for a specific reason or of a switch statement where the break statements are not put after some cases to give rise to fall-through conditions. In order to circumvent errors and warnings on such conditions, C++ provides attributes such as [maybe_unused] and [fallthrough] that prevent the compiler from generating warnings or errors.Â
CPP
#include <iostream>
#include <string>
int main()
{
// Set debug mode in compiler or 'R'
[[maybe_unused]] char mg_brk = 'D';
// Compiler does not emit any warnings
// or error on this unused variable
}
List of Standard Attributes in C++
1. noreturn: indicates that the function does not return a value
Usage:
[[noreturn]] void f();
While looking at the code above, the question arises what is the point of having noreturn when the return type is actually void? If a function has a void type, then it actually returns to the caller without a value but if the case is such that the function never returns back to the caller (for example an infinite loop) then adding a noreturn attribute gives hints to the compiler to optimise the code or generate better warnings. Example:Â
CPP
#include <iostream>
#include <string>
[[noreturn]] void f()
{
// Some code that does not return
// back the control to the caller
// In this case the function returns
// back to the caller without a value
// This is the reason why the
// warning "noreturn' function does return' arises
}
void g() { std::cout << "Code is intended to reach here"; }
int main()
{
f();
g();
}
Warning:
main.cpp: In function 'void f()':
main.cpp:8:1: warning: 'noreturn' function does return
}
^
2. deprecated: Indicates that the name or entity declared with this attribute has become obsolete and must not be used for some specific reason. This attribute can be applied to namespaces, functions, classes structures or variables. Usage:
[[deprecated("Reason for deprecation")]]
// For Class/Struct/Union
struct [[deprecated]] S;
// For Functions
[[deprecated]] void f();
// For namespaces
namespace [[deprecated]] ns{}
// For variables (including static data members)
[[deprecated]] int x;
Example:
CPP14
#include <iostream>
#include <string>
[[deprecated("Susceptible to buffer overflow")]] void
gets(char* str)
{
// Code for gets dummy
// (Although original function has
// char* as return type)
}
void gets_n(std::string& str)
{
// Dummy code
char st[100];
std::cout << "Successfully Executed";
std::cin.getline(st, 100);
str = std::string(st);
// Code for new gets
}
int main()
{
char a[100];
gets(a);
// std::string str;
// gets_n(str);
}
Warning:
main.cpp: In function 'int main()':
main.cpp:26:9: warning: 'void gets(char*)' is deprecated:
Susceptible to buffer overflow [-Wdeprecated-declarations]
gets(a);
^
3. nodiscard: The entities declared with nodiscard should not have their return values ignored by the caller. Simply saying if a function returns a value and is marked nodiscard then the return value must be utilized by the caller and not discarded.
Usage:
// Functions
[[nodiscard]] void f();
// Class/Struct declaration
struct [[nodiscard]] my_struct{};
The main difference between nodiscard with functions and nodiscard with struct/class declaration is that in case of function, nodiscard applies to that particular function only which is declared no discard, whereas in case of class/struct declaration nodiscard applies to every single function that returns the nodiscard marked object by value.
Example:Â
CPP
#include <iostream>
#include <string>
// Return value must be utilized by the caller
[[nodiscard]] int f() { return 0; }
class [[nodiscard]] my_class{};
// Automatically becomes nodiscard marked
my_class fun() { return my_class(); }
int main()
{
int x{ 1 };
// No error as value is utilised
// x= f();
// Error : Value is not utilised
f();
// Value not utilised error
// fun() ;
return x;
}
Warning:
prog.cpp:5:21: warning: 'nodiscard' attribute directive ignored [-Wattributes]
[[nodiscard]] int f()
^
prog.cpp:10:20: warning: 'nodiscard' attribute directive ignored [-Wattributes]
class[[nodiscard]] my_class{};
^
4. maybe_unused: Used to suppress warnings on any unused entities (For eg: An unused variable or an unused argument to a function). Usage:
//Variables
[[maybe_unused]] bool log_var = true;
//Functions
[[maybe_unused]] void log_without_warning();
//Function arguments
void f([[maybe_unused]] int a, int b);
Example:
CPP
#include <iostream>
#include <string>
int main()
{
// Set debug mode in compiler or 'R'
[[maybe_unused]] char mg_brk = 'D';
// Compiler does not emit any warnings
// or error on this unused variable
}
5. fallthrough: [[fallthrough]] indicates that a fallthrough in a switch statement is intentional. Missing a break or return in a switch statement is usually considered a programmer's error but in some cases fallthrough can result in some very terse code and hence it is used. Note: Unlike other attributes a fallthrough requires a semicolon after it is declared. Example:Â
CPP
void process_alert(Alert alert)
{
switch (alert) {
case Alert::Red:
evacuate();
// Compiler emits a warning here
// thinking it is done by mistake
case Alert::Orange:
trigger_alarm();
// this attribute needs semicolon
[[fallthrough]];
// Warning suppressed by [[fallthrough]]
case Alert::Yellow:
record_alert();
return;
case Alert::Green:
return;
}
}
6. likely: For optimisation of certain statements that have more probability to execute than others. Likely is now available in latest version of GCC compiler for experimentation purposes.
ExampleÂ
CPP
int f(int i)
{
switch (i) {
case 1:
[[fallthrough]];
[[likely]] case 2 : return 1;
}
return 2;
}
7. no_unique_address: Indicates that this data member need not have an address distinct from all other non-static data members of its class. This means that if the class consist of an empty type then the compiler can perform empty base optimisation on it.
Example:Â
CPP
// empty class ( No state!)
struct Empty {
};
struct X {
int i;
Empty e;
};
struct Y {
int i;
[[no_unique_address]] Empty e;
};
int main()
{
// the size of any object of
// empty class type is at least 1
static_assert(sizeof(Empty) >= 1);
// at least one more byte is needed
// to give e a unique address
static_assert(sizeof(X) >= sizeof(int) + 1);
// empty base optimization applied
static_assert(sizeof(Y) == sizeof(int));
}
8. expects: It specifies the conditions (in form of contract) that the arguments must meet for a particular function to be executed.
Usage:
return_type func ( args...) [[expects : precondition]]
Example:
CPP
void list(node* n)[[expects:n != nullptr]]
Violation of the contract results in invocation of violation handler or if not specified then std::terminate()
Difference Between Standard and Non-Standard Attributes
The following table lists the common differences between the standard and non standard attributes:
|
Specified by the standard and Are present In all the compilers | Provided by the compiler vendors And are not present across all compilers |
Code is completely portable Without any warnings or issues | Although code becomes portable (since C++17) for non-standard attributes in "standard syntax" some warnings/errors still creep in the compilers. |
Attributes are written inside the Standard syntax [[atr]] | Some of the attributes are written inside the non Standard syntax and others are written within a compiler specific keyword like declspec() or __attribute__ |
Standard attributes are not present In any enclosing namespace | Nonstandard attributes are written in standard syntax With their enclosing namespace [[namespace::attr]] |
Changes since C++11
1. Ignoring unknown attributes: Since C++17, one of the major changes introduced for the attribute feature in C++ were regarding the clarification of unknown attributes by the compiler. In C++11 or 14, if an attribute was not recognized by the compiler, then it would produce an error and prevent the code from getting compiled. As a workaround, the programmer had to remove the attribute from the code to make it work. This introduced a major issue for portability. Apart from the standard attributes none of the vendor-specific attributes could be used, as the code would break. This prevented the actual use of this feature. As a solution, the standard made it compulsory for all the compilers to ignore the attributes that were not defined by them. This allowed the programmers to use vendor-specific attributes freely in their code and ensure that the code was still portable. Most of the compilers supporting C++17 now ignore the undefined attributes and produce a warning on when encountered. This allows the programmers to make the code more flexible as now, they can specify multiple attributes for the same operation under different vendor's namespaces. (Support: MSVC(NOT YET), GCC, CLANG (YES)).
Example:Â
CPP
// Here the attributes will work on their respective
[[msvc::deprecated]][[gnu::deprecated]] char* gets(char* str) compilers
2. Use of attribute namespaces without repetition: In C++17 some of the rules regarding the use of "non-standard" attributes were relaxed. One such case is that of prefixing namespaces with a subsequent non-standard attribute. In C++11 or 14 when multiple attributes were written together each one of them had to be prefixed with their enclosing namespaces which gave rise to the pattern of code as shown below.Â
CPP
[[ gnu::always_inline, gnu::const, gnu::hot, nodiscard ]] int f();
Looking at the code above, it can be seen that it seems bloated and cluttered. So the committee decided to "simplify the case when using multiple attributes" together. As of now, it is not mandatory for the programmer to prefix the namespace again and again with subsequent attributes being used together. This gives rise to the pattern of code shown below which looks clean and understandable.Â
CPP
[[using gnu:const, always_inline]] int f() { return 0; }
3. Multiple attributes over a particular piece of code: Several attributes can now be applied to a certain piece of code in C++. The compiler, in that case, evaluates each of the attributes in the order they are written. This allows the programmers to write pieces of code that can contain multiple constraints. Example:Â
CPP
#include <iostream>
// Not implemented by compilers as of now
// but will be implemented in the future
[[nodiscard]] int f(int i)[[expects:i > 0]]
{
std::cout << " Always greater than 0!"
<< " and return val must "
<< "always be utilized";
}
Difference Between Attributes in C++ and C#
There is a notable difference between attributes in C# and C++. In the case of C#, the programmer can define new attributes by deriving from System.Attribute; whereas in C++, the meta information is fixed by the compiler and cannot be used to define new user-defined attributes. This restriction is placed to prevent the language from evolving into a new form which could have made the language more complicated.
Similar Reads
Python: AttributeError
In every programming language, if we develop new programs, there is a high chance of getting errors or exceptions. These errors yield to the program not being executed. One of the error in Python mostly occurs is "AttributeError". AttributeError can be defined as an error that is raised when an attr
3 min read
Abstraction in C++
Data abstraction is one of the most essential and important features of object-oriented programming in C++. Abstraction means displaying only essential information and ignoring the details. Data abstraction refers to providing only essential information about the data to the outside world, ignoring
4 min read
Character Arithmetic in C++
Character arithmetic in C++ involves performing mathematical operations on characters. In C++, characters are represented using the char data type, which is essentially an integer type that stores ASCII values. In this article, we will discuss character arithmetic and how to perform character arithm
2 min read
Avoid Bugs Using Modern C++
C++ has more constructs that can cause undefined behavior or exceptions as compared to languages like Java and Python. This is because C++ was developed with a primary objective to include classes in C and hence, support bug-inviting features such as pointers, macros, etc. It is also devoid of tools
3 min read
array::at() in C++ STL
Array classes are generally more efficient, light-weight and reliable than C-style arrays. The introduction of array class from C++11 has offered a better alternative for C-style arrays. array::at() This function is used to return the reference to the element present at the position given as the par
2 min read
string at() in C++
The std::string::at() in C++ is a built-in function of std::string class that is used to extract the character from the given index of string. In this article, we will learn how to use string::at() in C++.Syntaxstr.at(idx)Parametersidx: Index at which we have to find the character.Return ValueReturn
1 min read
How to Get a List of Class Attributes in Python?
A class is a user-defined blueprint or prototype from which objects are created. Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to
4 min read
C++ Variables
In C++, variable is a name given to a memory location. It is the basic unit of storage in a program. The value stored in a variable can be accessed or changed during program execution.Creating a VariableCreating a variable and giving it a name is called variable definition (sometimes called variable
4 min read
C++ Programming Basics
C++ is a general-purpose programming language and is widely used nowadays for competitive programming. It has imperative, object-oriented, and generic programming features. C++ runs on lots of platforms like Windows, Linux, Unix, Mac, etc.Before explaining the basics of C++, we would like to clarify
8 min read
Accessing Attributes and Methods in Python
In Python, attributes and methods define an object's behavior and encapsulate data within a class. Attributes represent the properties or characteristics of an object, while methods define the actions or behaviors that an object can perform. Understanding how to access and manipulate both attributes
3 min read