Question

Static const std::string Class Members in C++

cppstringclassconstantsstatic-members

Question

How can I create a private static string constant for a C++ class, such as a shape factory, without using a global #define?

For example, I would like code similar to this:

class A {
private:
    static const std::string RECTANGLE = "rectangle";
};

However, GCC reports errors because in-class initialization of a static data member of type std::string is not allowed in older C++ standards. What is the standard-compliant way to declare and define a private (or public) string constant owned by a class?

Short Answer

A static const std::string belongs to the class rather than to individual objects. In C++11 through C++14, declare it inside the class and define it once in a .cpp file. In C++17 and later, use an inline static const member and initialize it directly in the class definition.

Concept

A static data member is one value shared by the entire class. It exists independently of any particular object.

A const data member cannot be changed after it is initialized. Combining the two gives a class-owned value that should remain fixed, such as a command name, a file extension, or a configuration key.

std::string is a non-integral object type. In older C++ standards, the class definition may declare a static member, but the storage and initializer must appear in exactly one source file. This rule prevents multiple definitions when a header is included by several .cpp files.

C++17 introduced inline variables. An inline static data member may be defined and initialized in a class definition, even when that definition is placed in a header. The linker treats equivalent inline definitions across translation units as one variable.

Avoid using #define for named constants. Macros have no scope, no type, and are processed before C++ compilation. A class member is scoped, typed, and respects access control.

Mental Model

Think of a class as a building.

  • A non-static member is an item in every apartment: every object gets its own copy.
  • A static member is a sign in the building lobby: there is one shared copy for every object.
  • const means the sign is permanently fixed after it is installed.

In pre-C++17 C++, the class declaration is the building plan that says the sign exists. The out-of-class definition in one .cpp file is the actual installation of the sign. In C++17, inline static allows the plan to include the installation safely in a header.

Take Quiz

Syntax and Examples

For C++11 to C++14, declare the member in the class header and define it once in a source file.

// ShapeFactory.h
#pragma once

#include <string>

class ShapeFactory {
private:
    static const std::string RECTANGLE;

public:
    static const std::string& rectangleName();
};
// ShapeFactory.cpp
#include "ShapeFactory.h"

const std::string ShapeFactory::RECTANGLE = "rectangle";

const std::string& ShapeFactory::rectangleName() {
    return RECTANGLE;
}

Notice the important parts of the definition:

const std::string ShapeFactory::RECTANGLE = "rectangle";
  • It is outside the class body.
  • It includes the class scope: ShapeFactory::.
  • It appears in exactly one .cpp file.
  • Do not repeat static in this definition.

For C++17 and later, define it directly in the class:

#include <string>

class ShapeFactory {
private:
    inline static const std::string RECTANGLE = "rectangle";
};

inline here does not mean the compiler must inline code. It means the variable may have identical definitions in multiple translation units, which makes this header-safe.

Step by Step Execution

Consider this C++11-or-later, pre-C++17 style:

// A.h
class A {
public:
    static const std::string RECTANGLE;
};

// A.cpp
const std::string A::RECTANGLE = "rectangle";

// main.cpp
#include "A.h"
#include <iostream>

int main() {
    std::cout << A::RECTANGLE << '\n';
}

Execution and build steps:

  1. The compiler reads A.h. It learns that A has one class-level member named RECTANGLE.
  2. The compiler reads A.cpp. The definition allocates and initializes the one std::string object for A::RECTANGLE.
  3. The compiler reads main.cpp. A::RECTANGLE refers to that declared class member.
  4. The linker connects the use in main.cpp to the single definition in A.cpp.
  5. When the program starts, the string is initialized to "rectangle" before main runs.
  6. main prints:
rectangle

Because the member is static, A::RECTANGLE can be accessed without constructing an A object.

Real World Use Cases

Class-owned string constants are useful when a name is part of a type's API or implementation.

  • Factory identifiers: "rectangle", "circle", or "triangle" used to select an object to create.
  • JSON field names: constants such as "user_id" and "created_at" prevent repeated string literals.
  • HTTP headers: a client may centralize names such as "Authorization" or "Content-Type".
  • Configuration keys: "database.host" or "cache.enabled" can be kept near the code that owns the setting.
  • File formats and extensions: a serializer can own values such as ".json" or "application/json".

Keeping these values in a relevant class gives them a useful scope, for example ShapeFactory::RECTANGLE, rather than exposing a generic global name.

Take Quiz

Real Codebase Usage

In production code, choose the representation based on whether you need an owning std::string object.

C++17 class constant

Use this when callers need a std::string or when the class API naturally owns the value.

class ApiRoutes {
public:
    inline static const std::string health = "/health";
};

Avoiding repeated literals

A factory can compare input against one canonical name:

std::unique_ptr<Shape> ShapeFactory::create(const std::string& name) {
    if (name == RECTANGLE) {
        return std::make_unique<Rectangle>();
    }

    return nullptr;
}

Prefer std::string_view for read-only text views

If the value is a fixed text literal and you only need to compare or read it, C++17 std::string_view avoids owning a dynamically managed string:

#include <string_view>

class ShapeFactory {
public:
    inline static constexpr std::string_view rectangle = "rectangle";
};

Use std::string_view carefully: it does not own character storage. A string literal is safe because it exists for the lifetime of the program. A view into a temporary std::string is not safe.

Common Mistakes

Initializing a non-integral static member in a pre-C++17 class definition

This is invalid before C++17:

class A {
    static const std::string RECTANGLE = "rectangle"; // Invalid before C++17
};

Fix it by declaring it in the class and defining it in one .cpp file, or compile as C++17 and add inline static.

Forgetting the out-of-class definition

This declaration alone is not enough if the value is used:

class A {
    static const std::string RECTANGLE;
};

Add one definition:

const std::string A::RECTANGLE = "rectangle";

Otherwise, the linker commonly reports an undefined reference.

Adding static again outside the class

Do not write this:

static const std::string A::RECTANGLE = "rectangle"; // Wrong

The member was declared static inside the class. The definition uses the member's existing linkage and only needs its type and qualified name.

Defining the member in a header before C++17

This causes multiple-definition linker errors when several .cpp files include the header:

// Header: do not do this before C++17
const std::string A::RECTANGLE = "rectangle";

Put the definition in one .cpp file. In C++17, use inline static in the class instead.

Returning a mutable copy unnecessarily

If an accessor is needed, returning const std::string& avoids copying:

static const std::string& rectangleName();

Return by value only when the caller should receive an independent string.

Using a macro for a scoped constant

#define RECTANGLE "rectangle"

This has no class scope and can conflict with another macro or identifier. Prefer a class member or a namespace-level constant.

Comparisons

OptionScopeOwns characters?Best use
static const std::stringClassYesA class-owned string object, especially when a std::string API is required
inline static const std::string (C++17+)ClassYesHeader-only or modern class definitions
inline static constexpr std::string_view (C++17+)ClassNoFixed literals used for reading and comparison
constexpr const char*Class or namespaceNoInteroperating with C APIs; less expressive than string_view
#define macroNo C++ scopeNoGenerally avoid for constants
Namespace inline constexpr std::string_viewNamespaceNoA shared constant not owned by one class

std::string owns its data and is convenient when an API requires a string object. std::string_view is lightweight but only borrows a sequence of characters, so its source data must outlive the view.

Take Quiz

Cheat Sheet

// C++11 through C++14: header
class A {
private:
    static const std::string rectangle;
};

// C++11 through C++14: exactly one .cpp file
const std::string A::rectangle = "rectangle";
// C++17+: header-safe class member
class A {
private:
    inline static const std::string rectangle = "rectangle";
};
// C++17+: efficient fixed literal
#include <string_view>

class A {
private:
    inline static constexpr std::string_view rectangle = "rectangle";
};

Rules:

  • Include <string> for std::string and <string_view> for std::string_view.
  • Use std::string, unless you intentionally use using std::string;.
  • Before C++17, define a non-inline static std::string in one .cpp file.
  • In an out-of-class member definition, write A::memberName and do not repeat static.
  • const prevents modification through that member name; private prevents outside code from naming it.

FAQ

Can a static std::string be initialized inside a class?

Yes in C++17 or later with inline static const std::string. Before C++17, declare it in the class and define it in one .cpp file.

Why does static const int sometimes work inside a class without a .cpp definition?

Older C++ rules allow in-class initializers for certain integral or enumeration constants when initialized with constant expressions. std::string is an object type, so it does not follow that special rule.

Should I use constexpr std::string?

Not for a portable C++17 solution. std::string was not generally usable as a compile-time constant in the way string literals are. For a fixed literal, constexpr std::string_view is often the better choice.

Is static constexpr std::string_view private by default in a class?

Yes. Members before a public: label in a class are private by default.

Does const make the std::string compile-time constant?

No. const means it cannot be modified after initialization. It does not necessarily mean compile-time evaluation.

Can outside code read a private static constant?

No, not directly. Provide a public function, such as rectangleName(), if outside code needs access.

Do I need an object to access a static member?

No. Use the class name: ShapeFactory::rectangleName() or, when public, ShapeFactory::RECTANGLE.

Take Quiz

Mini Project

Description

Build a small shape factory that accepts a text name and creates either a rectangle or a circle. The factory keeps its accepted names as private, class-owned constants so spelling is centralized and macros are unnecessary.

Goal

Create a factory with private static string constants and a public method that maps a shape name to a result.

Requirements

  1. Create a ShapeFactory class with private names for "rectangle" and "circle".
  2. Use C++17 inline static constants.
  3. Add a public create function that accepts a const std::string&.
  4. Return a descriptive result for known shape names.
  5. Return an error message for an unknown name.
Take Quiz

Keep learning

Related questions

View more