Open In App

attributes in C++

Improve
Improve
Like Article
Like
Save
Share
Report

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:

Standard Attributes 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.



Last Updated : 30 Jan, 2024
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads