CodeNebula
C++17coreadvanced

Non-type Template Parameters auto

C++17 · Practical Modern · Compiler flag: -std=c++17

Explanation

Non-type template parameters can be declared with `auto`, letting the compiler deduce their type. This makes templates more flexible and reduces duplication when the exact non-type parameter type is not important to the template logic.

Real-World Scenario

Write a generic compile-time constant wrapper that works with any integral or pointer type without explicit type specification.

Quick Snippet

snippet.cpp
template<auto Value>
struct Constant {
    static constexpr auto value = Value;
};

Constant<42>    intConst;    // Value deduced as int
Constant<'A'>   charConst;   // Value deduced as char
Constant<true>  boolConst;   // Value deduced as bool

static_assert(Constant<42>::value == 42);
static_assert(Constant<'A'>::value == 65);

Full Example

nttp_auto.cpp
// CodeNebula - C++17 Example: auto Non-Type Template Parameters
// Compile: g++ -std=c++17 -Wall -Wextra -o example nttp_auto.cpp

// Demonstrates C++17's auto for non-type template parameters. Instead of
// specifying the exact type (int, char, bool, etc.), you use auto and the
// compiler deduces it. This simplifies generic code that works with any
// compile-time constant.

#include <iostream>
#include <type_traits>
#include <string>
#include <array>

// ------------------------------------------------------------------
// 1. Basic auto NTTP: type is deduced from the argument
// ------------------------------------------------------------------
template <auto Value>
struct Constant {
    using type = decltype(Value);

    static void print() {
        std::cout << "  Value = " << Value
                  << ", type = " << typeid(type).name() << std::endl;
    }
};

// ------------------------------------------------------------------
// 2. auto NTTP for compile-time dispatch
// ------------------------------------------------------------------
template <auto N>
constexpr auto doubled() {
    return N + N;  // works for int, char, bool, etc.
}

// ------------------------------------------------------------------
// 3. Heterogeneous compile-time list using auto...
// ------------------------------------------------------------------
template <auto... Values>
struct ValueList {
    static constexpr std::size_t size = sizeof...(Values);

    static void print() {
        std::cout << "  [";
        ((std::cout << " " << Values), ...);
        std::cout << " ] (size=" << size << ")" << std::endl;
    }
};

// ------------------------------------------------------------------
// 4. auto NTTP for function pointers
// ------------------------------------------------------------------
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

template <auto Func>
int apply(int a, int b) {
    return Func(a, b);
}

// ------------------------------------------------------------------
// 5. auto NTTP for member pointers
// ------------------------------------------------------------------
struct Config {
    int width = 800;
    int height = 600;
    bool fullscreen = false;
};

template <auto MemberPtr>
auto get_member(const Config& cfg) {
    return cfg.*MemberPtr;
}

// ------------------------------------------------------------------
// 6. Compile-time factorial with auto NTTP
// ------------------------------------------------------------------
template <auto N>
constexpr auto factorial() {
    if constexpr (N <= 1) return decltype(N){1};
    else return N * factorial<N - 1>();
}

int main()
{
    // 1. Basic auto NTTP
    std::cout << "=== auto NTTP basics ===" << std::endl;
    Constant<42>::print();        // int
    Constant<'A'>::print();       // char
    Constant<true>::print();      // bool
    Constant<100UL>::print();     // unsigned long

    // 2. Doubled with auto NTTP
    std::cout << "\n=== doubled<N>() ===" << std::endl;
    std::cout << "  doubled<21>()   = " << doubled<21>() << std::endl;
    std::cout << "  doubled<'A'>()  = " << static_cast<char>(doubled<'A'>()) << std::endl;
    std::cout << "  doubled<true>() = " << doubled<true>() << std::endl;

    // 3. Heterogeneous value list (auto...)
    std::cout << "\n=== ValueList<auto...> ===" << std::endl;
    ValueList<1, 2, 3, 4, 5>::print();
    ValueList<'x', 'y', 'z'>::print();

    // 4. Function pointer as NTTP
    std::cout << "\n=== function pointer NTTP ===" << std::endl;
    std::cout << "  apply<add>(3, 4)      = " << apply<add>(3, 4) << std::endl;
    std::cout << "  apply<multiply>(3, 4) = " << apply<multiply>(3, 4) << std::endl;

    // 5. Member pointer as NTTP
    std::cout << "\n=== member pointer NTTP ===" << std::endl;
    Config cfg;
    std::cout << "  width:  " << get_member<&Config::width>(cfg) << std::endl;
    std::cout << "  height: " << get_member<&Config::height>(cfg) << std::endl;

    // 6. Compile-time factorial
    std::cout << "\n=== compile-time factorial ===" << std::endl;
    std::cout << "  5!  = " << factorial<5>() << std::endl;
    std::cout << "  10! = " << factorial<10>() << std::endl;

    return 0;
}

/*
Expected output (type names are compiler-dependent):

=== auto NTTP basics ===
  Value = 42, type = i
  Value = A, type = c
  Value = 1, type = b
  Value = 100, type = m

=== doubled<N>() ===
  doubled<21>()   = 42
  doubled<'A'>()  = (char with value 130, displayed as a character)
  doubled<true>() = 2

=== ValueList<auto...> ===
  [ 1 2 3 4 5 ] (size=5)
  [ x y z ] (size=3)

=== function pointer NTTP ===
  apply<add>(3, 4)      = 7
  apply<multiply>(3, 4) = 12

=== member pointer NTTP ===
  width:  800
  height: 600

=== compile-time factorial ===
  5!  = 120
  10! = 3628800
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example nttp_auto.cpp