CodeNebula
C++17coreadvanced

Using-declaration for Parameter Packs

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

Explanation

Extends `using` declarations to work with parameter packs, allowing a class to inherit or expose members from multiple base classes in a single statement. This is particularly powerful when building variadic overload sets from lambda-based visitors.

Real-World Scenario

Construct an overloaded visitor for `std::variant` by combining multiple lambdas into one callable object.

Quick Snippet

snippet.cpp
#include <variant>

template<class... Ts>
struct overloaded : Ts... {
    using Ts::operator()...;  // Using-declaration with parameter pack
};

// Deduction guide
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

std::variant<int, double, std::string> v = "hello";
std::visit(overloaded{
    [](int i)              { std::cout << "int: " << i << "\n"; },
    [](double d)           { std::cout << "double: " << d << "\n"; },
    [](const std::string& s) { std::cout << "string: " << s << "\n"; }
}, v);

Full Example

using_parameter_packs.cpp
// CodeNebula - C++17 Example: Using-declaration for Parameter Packs
// Compile: g++ -std=c++17 -Wall -Wextra -o example using_parameter_packs.cpp

// Demonstrates C++17's ability to expand parameter packs in using-declarations.
// This allows inheriting constructors or operators from multiple bases in a
// single using statement: using Bases::operator()...;

#include <iostream>
#include <string>
#include <variant>

// ------------------------------------------------------------------
// 1. Overloaded callable: the classic "overloaded" pattern
// ------------------------------------------------------------------
// Each lambda becomes a base class; we inherit all operator() overloads.
template <typename... Ts>
struct overloaded : Ts... {
    using Ts::operator()...;  // C++17: pack expansion in using-declaration
};

// Deduction guide (C++17 CTAD)
template <typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

// ------------------------------------------------------------------
// 2. Multiple base classes with different named methods
// ------------------------------------------------------------------
struct Printer {
    void print(const std::string& msg) const {
        std::cout << "  [Printer] " << msg << std::endl;
    }
};

struct Logger {
    void log(const std::string& msg) const {
        std::cout << "  [Logger] " << msg << std::endl;
    }
};

struct Alerter {
    void alert(const std::string& msg) const {
        std::cout << "  [Alerter] " << msg << std::endl;
    }
};

// Combine all three into one class, bringing in all methods
template <typename... Bases>
struct CombinedService : Bases... {
    using Bases::print...;    // Not applicable here since names differ,
    // but we can use constructor inheritance:
    // using Bases::Bases...;  // would bring in all base constructors
};

// ------------------------------------------------------------------
// 3. Inheriting constructors from multiple bases
// ------------------------------------------------------------------
struct FromInt {
    int value;
    FromInt(int v) : value(v) {}
};

struct FromString {
    std::string text;
    FromString(std::string s) : text(std::move(s)) {}
};

struct FromDouble {
    double number;
    FromDouble(double d) : number(d) {}
};

template <typename... Bases>
struct MultiConstructor : Bases... {
    using Bases::Bases...;   // C++17: inherit all constructors from all bases
};

int main()
{
    // ------------------------------------------------------------------
    // 1. Overloaded pattern with std::variant
    // ------------------------------------------------------------------
    std::cout << "=== overloaded visitor (using...pack) ===" << std::endl;

    std::variant<int, double, std::string> v1 = 42;
    std::variant<int, double, std::string> v2 = 3.14;
    std::variant<int, double, std::string> v3 = std::string("hello");

    auto visitor = overloaded{
        [](int i)               { std::cout << "  int: " << i << std::endl; },
        [](double d)            { std::cout << "  double: " << d << std::endl; },
        [](const std::string& s){ std::cout << "  string: " << s << std::endl; }
    };

    std::visit(visitor, v1);
    std::visit(visitor, v2);
    std::visit(visitor, v3);

    // ------------------------------------------------------------------
    // 2. Inline visitor with overloaded
    // ------------------------------------------------------------------
    std::cout << "\n=== inline visitor ===" << std::endl;
    std::variant<int, std::string> data = std::string("C++17");

    std::visit(overloaded{
        [](int n)               { std::cout << "  number: " << n << std::endl; },
        [](const std::string& s){ std::cout << "  text: " << s << std::endl; }
    }, data);

    // ------------------------------------------------------------------
    // 3. Inheriting constructors from multiple bases
    // ------------------------------------------------------------------
    std::cout << "\n=== inherited constructors (using Bases::Bases...) ===" << std::endl;

    using Multi = MultiConstructor<FromInt, FromString, FromDouble>;

    Multi m1(42);                       // calls FromInt(int)
    Multi m2(std::string("test"));      // calls FromString(string)
    Multi m3(2.718);                    // calls FromDouble(double)

    std::cout << "  From int:    " << m1.value << std::endl;
    std::cout << "  From string: " << m2.text << std::endl;
    std::cout << "  From double: " << m3.number << std::endl;

    return 0;
}

/*
Expected output:

=== overloaded visitor (using...pack) ===
  int: 42
  double: 3.14
  string: hello

=== inline visitor ===
  text: C++17

=== inherited constructors (using Bases::Bases...) ===
  From int:    42
  From string: test
  From double: 2.718
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example using_parameter_packs.cpp