attributes in C++

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++

  • 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

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    int f(int i)
    {
        if (i > 0)
            return i;
        else
            return -1;
      
        // Code
    }

    chevron_right

    
    

    It increases the readability of your code and avoids the clutter that is written inside the function for argument checking.

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    int f(int i)[[expects:i > 0]]
    {
        // Code
    }

    chevron_right

    
    

  • 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.

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    int f(int i)
    {
        switch (i) {
        case 1:
            [[fallthrough]];
            [[likely]] case 2 : return 1;
        }
        return -1;
    }

    chevron_right

    
    

    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]

  • 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.

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    #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
    }

    chevron_right

    
    

List of standard attributes in C++

    C++11

  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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    #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 intented to reach here";
    }
      
    int main()
    {
        f();
        g();
    }

    chevron_right

    
    

    Warning:

    main.cpp: In function 'void f()':
    main.cpp:8:1: warning: 'noreturn' function does return
     }
     ^
    
  2. C++14

  3. 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    #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);
    }

    chevron_right

    
    

    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);
             ^
    
  4. C++17

  5. 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    #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;
    }

    chevron_right

    
    

    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{};
                        ^
    
  6. 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_used]] bool log_var = true;
    
    //Functions
    [[maybe_unused]] void log_without_warning();
    
    //Function arguments 
    void f([[maybe_unused]] int a, int b);
    

    Example:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    #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
    }

    chevron_right

    
    

  7. 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    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;
        }
    }

    chevron_right

    
    

  8. Upcoming C++20 attributes



  9. 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

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    int f(int i)
    {
        switch (i) {
        case 1:
            [[fallthrough]];
            [[likely]] case 2 : return 1;
        }
        return 2;
    }

    chevron_right

    
    

  10. 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    // 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));
    }

    chevron_right

    
    

  11. 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    void list(node* n)[[expects:n != nullptr]]

    chevron_right

    
    

    Violation of the contract results in invocation of violation handler or if not specified then std::terminate()

Difference between 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

  • 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    // Here the attributes will work on their respective
    [[msvc::deprecated]][[gnu::deprecated]] char* gets(char* str) compilers

    chevron_right

    
    

  • 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.

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    [[ gnu::always_inline, gnu::const, gnu::hot, nodiscard ]] int f();

    chevron_right

    
    

    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.

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    [[using gnu:const, always_inline]] int f() { return 0; }

    chevron_right

    
    

  • 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:

    filter_none

    edit
    close

    play_arrow

    link
    brightness_4
    code

    #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";
    }

    chevron_right

    
    

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.



My Personal Notes arrow_drop_up

I am a C/C++ developer along with knowledge in C# XAML VBNET

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.