skip to content
Mehdi Mehdikhani
Table of Contents

C++11 attributes provide a standardized way to give the compiler extra information about your code. They’re like compiler pragmas, but portable and standard.

What Attributes Actually Are

Attributes use the [[attribute]] syntax and provide hints to the compiler:

[[deprecated]]
void old_function() {
// Compiler will warn when this is used
}
[[nodiscard]]
int calculate() {
return 42; // Compiler warns if return value is ignored
}

They don’t change behavior, just provide additional information.

Standard Attributes

C++11 started with a few, and each standard added more:

// C++11
[[noreturn]]
void terminate_program() {
std::exit(1); // Never returns normally
}
// C++14
[[deprecated("Use new_function instead")]]
void legacy_function();
// C++17
[[nodiscard]]
ErrorCode process_data();
[[fallthrough]] // In switch statements
switch (value) {
case 1:
do_something();
[[fallthrough]];
case 2:
do_something_else();
break;
}
// C++20
[[likely]]
if (condition) { // Hint that this branch is more probable
// Hot path
}

When I Actually Use Attributes

Most of the time, I use attributes for:

  1. Deprecating functions: Gradual migration from old APIs:
class DatabaseManager {
public:
[[deprecated("Use execute_query with timeout parameter")]]
ResultSet execute_query(const std::string& sql) {
return execute_query(sql, std::chrono::seconds(30));
}
ResultSet execute_query(const std::string& sql, std::chrono::seconds timeout) {
// New implementation
}
};
  1. Preventing ignored return values: For functions where the return value matters:
class FileManager {
public:
[[nodiscard]]
bool create_directory(const std::string& path) {
// Returns false on failure - caller should check!
}
[[nodiscard]]
std::optional<std::string> read_file(const std::string& filename) {
// Returns empty optional on failure
}
};
// This will generate a warning:
file_manager.create_directory("/tmp/mydir"); // Ignoring return value!
// Correct usage:
if (!file_manager.create_directory("/tmp/mydir")) {
// Handle error
}
  1. Switch statement fallthrough: Making intentional fallthrough clear:
enum class TokenType { Number, Operator, Identifier, Keyword };
void process_token(TokenType type) {
switch (type) {
case TokenType::Number:
handle_number();
break;
case TokenType::Identifier:
check_if_builtin();
[[fallthrough]]; // Intentional fallthrough to keyword handling
case TokenType::Keyword:
handle_identifier_or_keyword();
break;
case TokenType::Operator:
handle_operator();
break;
}
}
  1. Performance hints: Helping the compiler optimize:
bool is_valid_user(const User& user) {
[[likely]]
if (user.is_active && user.has_valid_session()) {
return true; // Most users are valid
}
[[unlikely]]
if (user.is_suspended()) {
log_security_event("Suspended user attempted access");
return false; // Rare case
}
return false;
}

Custom Attributes

Some compilers support additional attributes:

// GCC/Clang specific
[[gnu::hot]] // Function is called frequently
[[gnu::cold]] // Function is rarely called
[[gnu::pure]] // Function has no side effects
// MSVC specific
[[msvc::intrinsic]] // Compiler intrinsic function

These aren’t portable, so I use them sparingly.

Real World Example

Here’s how I use attributes in a network protocol parser:

class ProtocolParser {
public:
[[nodiscard]]
ParseResult parse_message(const std::byte* data, size_t length) {
if (length < MIN_MESSAGE_SIZE) {
[[unlikely]]
return ParseResult::InvalidLength;
}
// Parse message...
return ParseResult::Success;
}
[[deprecated("Use parse_message with explicit buffer management")]]
ParseResult parse_message(const std::string& data) {
return parse_message(reinterpret_cast<const std::byte*>(data.data()), data.size());
}
[[noreturn]]
void handle_fatal_error(const std::string& message) {
log_critical_error(message);
std::terminate();
}
};

Attribute Syntax Rules

Attributes can go in different places:

// On declarations
[[deprecated]] void func();
// On types
void process([[maybe_unused]] int param);
// On statements
[[fallthrough]];
// Multiple attributes
[[deprecated, nodiscard]]
int calculate();

The Pattern I Follow

I use attributes to make intent explicit:

// Mark functions that should not have ignored return values
[[nodiscard]] bool initialize();
[[nodiscard]] ErrorCode process();
// Mark deprecated APIs with migration hints
[[deprecated("Use modern_api() instead")]] void legacy_api();
// Mark unlikely error paths
if (critical_resource == nullptr) {
[[unlikely]]
handle_out_of_memory();
}
// Mark functions that never return
[[noreturn]] void panic(const std::string& message);

Attributes improve code quality by making assumptions and contracts explicit. They help the compiler optimize and help other developers understand the code.