Namespaces
Variants

Using-declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

이 using 선언이 나타나는 선언 영역에 다른 곳에서 정의된 이름을 도입합니다. 관련된 다른 선언에 대해서는 using enum (C++20부터) using namespace 를 참조하십시오.

using typename (선택적) nested-name-specifier unqualified-id ; (C++17 이전)
using declarator-list ; (C++17 이후)
typename - 키워드 typename 은 using 선언이 클래스 템플릿의 기본 클래스로부터 멤버 타입을 도입할 때 종속 이름(dependent names) 을 해결하기 위해 필요한 경우 사용될 수 있음
nested-name-specifier - 이름과 범위 확인 연산자 :: 의 시퀀스로, 범위 확인 연산자로 끝남. 단일 :: 는 전역 네임스페이스를 참조함
unqualified-id - 식별자 표현(id-expression)
declarator-list - typename (선택사항) nested-name-specifier unqualified-id 의 하나 이상의 선언자(declarators)를 쉼표로 구분한 목록. 일부 또는 모든 선언자는 팩 확장(pack expansion) 을 나타내기 위해 말줄임표 ... 가 뒤따를 수 있음

목차

설명

using 선언은 네임스페이스 멤버를 다른 네임스페이스와 블록 스코프로 도입하거나, 기본 클래스 멤버를 파생 클래스 정의로 도입하는 데 사용할 수 있습니다 , 또는 enumerators 를 네임스페이스, 블록, 클래스 스코프로 도입하는 데 사용할 수 있습니다 (since C++20) .

둘 이상의 using-선언자를 가진 using-선언은 하나의 using-선언자를 가진 해당 using-선언 시퀀스와 동등합니다.

(since C++17)

네임스페이스 및 블록 범위에서

Using-declarations 는 다른 네임스페이스의 멤버를 현재 네임스페이스나 블록 범위로 도입합니다.

#include <iostream>
#include <string>
using std::string;
int main()
{
    string str = "Example";
    using std::cout;
    cout << str;
}

자세한 내용은 namespace 를 참조하십시오.

클래스 정의 내에서

using 선언은 기저 클래스의 멤버를 파생 클래스 정의 내로 도입합니다. 예를 들어 기저의 protected 멤버를 파생의 public 멤버로 노출하는 경우가 있습니다. 이 경우 nested-name-specifier 는 정의 중인 클래스의 기저 클래스를 지정해야 합니다. 이름이 기저 클래스의 오버로드된 멤버 함수 이름인 경우, 해당 이름을 가진 모든 기저 클래스 멤버 함수가 도입됩니다. 파생 클래스에 이미 동일한 이름, 매개변수 목록 및 한정자를 가진 멤버가 있는 경우, 파생 클래스 멤버는 기저 클래스에서 도입된 멤버를 숨기거나 재정의합니다(충돌하지 않음).

#include <iostream>
struct B
{
    virtual void f(int) { std::cout << "B::f\n"; }
    void g(char)        { std::cout << "B::g\n"; }
    void h(int)         { std::cout << "B::h\n"; }
protected:
    int m; // B::m은 protected입니다
    typedef int value_type;
};
struct D : B
{
    using B::m;          // D::m은 public입니다
    using B::value_type; // D::value_type은 public입니다
    using B::f;
    void f(int) override { std::cout << "D::f\n"; } // D::f(int)는 B::f(int)를 재정의합니다
    using B::g;
    void g(int) { std::cout << "D::g\n"; } // g(int)와 g(char) 모두 보입니다
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int)는 B::h(int)를 숨깁니다
};
int main()
{
    D d;
    B& b = d;
//  b.m = 2;  // 오류: B::m은 protected입니다
    d.m = 1;  // protected B::m은 public D::m으로 접근 가능합니다
    b.f(1);   // 파생 클래스의 f()를 호출합니다
    d.f(1);   // 파생 클래스의 f()를 호출합니다
    std::cout << "----------\n";
    d.g(1);   // 파생 클래스의 g(int)를 호출합니다
    d.g('a'); // 기본 클래스의 g(char)를 호출하며, using B::g;를 통해 노출됩니다
    std::cout << "----------\n";
    b.h(1);   // 기본 클래스의 h()를 호출합니다
    d.h(1);   // 파생 클래스의 h()를 호출합니다
}

출력:

D::f
D::f
----------
D::g
B::g
----------
B::h
D::h

생성자 상속

만약 using-declaration 이 정의 중인 클래스의 직접 기반 클래스 생성자를 참조하는 경우(예: using Base :: Base ; ), 해당 기반 클래스의 모든 생성자들(멤버 접근 제어는 무시)은 파생 클래스를 초기화할 때 오버로드 해결을 위해 visible 상태가 됩니다.

오버로드 해결이 상속된 생성자를 선택하면, 해당하는 기본 클래스의 객체를 생성하는 데 사용될 때 접근 가능한 경우 접근 가능합니다: 이를 도입한 using 선언의 접근성은 무시됩니다.

이러한 파생 클래스의 객체를 초기화할 때 오버로드 해결이 상속된 생성자 중 하나를 선택하면, 해당 생성자가 상속된 Base 하위 객체는 상속된 생성자를 사용하여 초기화되고, Derived 의 다른 모든 기반 클래스와 멤버들은 기본 설정된 기본 생성자에 의해 초기화된 것처럼 처리됩니다(기본 멤버 초기화자가 제공된 경우 사용되며, 그렇지 않으면 기본 초기화가 수행됩니다). 전체 초기화는 단일 함수 호출로 취급됩니다: 상속된 생성자의 매개변수 초기화는 파생 객체의 어떤 기반 클래스나 멤버의 초기화보다 먼저 순서가 지정됩니다 .

struct B1 { B1(int, ...) {} };
struct B2 { B2(double)   {} };
int get();
struct D1 : B1
{
    using B1::B1; // B1(int, ...)를 상속함
    int x;
    int y = get();
};
void test()
{
    D1 d(2, 3, 4); // OK: B1은 B1(2, 3, 4) 호출로 초기화되고,
                   // d.x는 기본 초기화되며(초기화가 수행되지 않음),
                   // d.y는 get() 호출로 초기화됨
    D1 e;          // 오류: D1은 기본 생성자가 없음
}
struct D2 : B2
{
    using B2::B2; // B2(double)를 상속함
    B1 b;
};
D2 f(1.0); // 오류: B1은 기본 생성자가 없음
struct W { W(int); };
struct X : virtual W
{
    using W::W; // W(int)를 상속함
    X() = delete;
};
struct Y : X
{
    using X::X;
};
struct Z : Y, virtual W
{
    using Y::Y;
};
Z z(0); // OK: Y의 초기화가 X의 기본 생성자를 호출하지 않음

만약 Base 기본 클래스 하위 객체가 Derived 객체의 일부로 초기화되지 않는 경우 (즉, Base 가상 기본 클래스 이고 Derived 객체가 최종 파생 객체 가 아닌 경우), 상속된 생성자의 호출(인수 평가 포함)은 생략됩니다:

struct V
{
    V() = default;
    V(int);
};
struct Q { Q(); };
struct A : virtual V, Q
{
    using V::V;
    A() = delete;
};
int bar() { return 42; }
struct B : A
{
    B() : A(bar()) {} // 정상
};
struct C : B {};
void foo()
{
    C c; // "bar"가 호출되지 않음. V 서브오브젝트가
         // B의 일부로 초기화되지 않기 때문
         // (V 서브오브젝트는 C의 일부로 초기화됨.
         //  "c"가 최종 파생 객체이기 때문)
}

생성자가 Base 타입의 여러 기본 클래스 하위 객체로부터 상속된 경우, 다중 상속된 비정적 멤버 함수와 유사하게 프로그램의 형식이 올바르지 않습니다:

struct A { A(int); };
struct B : A { using A::A; };
struct C1 : B { using B::B; };
struct C2 : B { using B::B; };
struct D1 : C1, C2
{
    using C1::C1;
    using C2::C2;
};
D1 d1(0); // 잘못된 형식: 서로 다른 B 기본 하위 객체로부터 상속된 생성자
struct V1 : virtual B { using B::B; };
struct V2 : virtual B { using B::B; };
struct D2 : V1, V2
{
    using V1::V1;
    using V2::V2;
};
D2 d2(0); // 올바른 형식: B 하위 객체가 하나만 존재합니다.
          // 이는 가상 B 기본 클래스를 초기화하며,
          //   A 기본 클래스를 초기화하고
          // 그런 다음 V1 및 V2 기본 클래스를
          //   기본값 생성자처럼 초기화합니다

다른 비정적 멤버 함수에 대한 using 선언과 마찬가지로, 상속된 생성자가 Derived 의 생성자 중 하나와 시그니처가 일치하면, Derived 에서 찾은 버전에 의해 lookup에서 숨겨집니다. Base 의 상속된 생성자 중 하나가 Derived 의 복사/이동 생성자와 일치하는 시그니처를 갖는 경우, 이는 Derived 의 복사/이동 생성자의 암시적 생성을 방해하지 않습니다 (그런 다음 상속된 버전을 숨기며, using operator= 와 유사합니다).

struct B1 { B1(int); };
struct B2 { B2(int); };
struct D2 : B1, B2
{
    using B1::B1;
    using B2::B2;
    D2(int); // OK: D2::D2(int)가 B1::B1(int)와 B2::B2(int)를 모두 가립니다
};
D2 d2(0);    // D2::D2(int)를 호출합니다

템플릿 클래스 내에서, using 선언이 종속 이름 을 참조하는 경우, 중첩 이름 지정자 의 최종 이름이 한정되지 않은 식별자 와 동일할 때 생성자를 지칭하는 것으로 간주됩니다.

template<class T>
struct A : T
{
    using T::T; // OK, T의 생성자들을 상속함
};
template<class T, class U>
struct B : T, A<U>
{
    using A<U>::A; // OK, A<U>의 생성자들을 상속함
    using T::A;    // T의 생성자를 상속하지 않음
                   // 비록 T가 A<>의 특수화일지라도
};
(C++11 이후)


범위 지정 열거자 도입

다른 네임스페이스의 멤버와 기본 클래스의 멤버 외에도, using 선언은 열거형 의 열거자를 네임스페이스, 블록, 클래스 범위로 도입할 수 있습니다.

using 선언은 범위 없는 열거자와도 사용할 수 있습니다.

enum class button { up, down };
struct S
{
    using button::up;
    button b = up; // OK
};
using button::down;
constexpr button non_up = down; // OK
constexpr auto get_button(bool is_up)
{
    using button::up, button::down;
    return is_up ? up : down; // OK
}
enum unscoped { val };
using unscoped::val; // OK, though needless
(C++20부터)

참고 사항

using 선언에서 명시적으로 언급된 이름만 선언적 범위로 이전됩니다: 특히, 열거형 타입 이름이 using 선언될 때 열거자들은 이전되지 않습니다.

using 선언은 네임스페이스를 참조할 수 없습니다 , 범위 있는 열거자 (C++20 이전) , 기본 클래스의 소멸자 또는 사용자 정의 변환 함수에 대한 멤버 템플릿의 특수화를 참조할 수 없습니다.

using 선언은 멤버 템플릿 특수화를 지정할 수 없습니다 ( template-id 는 문법적으로 허용되지 않음):

struct B
{
    template<class T>
    void f();
};
struct D : B
{
    using B::f;      // OK: 템플릿을 명명함
//  using B::f<int>; // 오류: 템플릿 특수화를 명명함
    void g() { f<int>(); }
};

using 선언은 종속 멤버 템플릿의 이름을 template-name 으로 도입하는 데에도 사용할 수 없습니다 ( 종속 이름 에 대한 template 디스앰비귀에이터는 허용되지 않습니다).

template<class X>
struct B
{
    template<class T>
    void f(T);
};
template<class Y>
struct D : B<Y>
{
//  using B<Y>::template f; // 오류: 분해자(disambiguator)가 허용되지 않음
    using B<Y>::f;          // 컴파일되지만 f는 템플릿 이름이 아님
    void g()
    {
//      f<int>(0);          // 오류: f가 템플릿 이름으로 알려져 있지 않아
                            // <가 템플릿 인자 목록을 시작하지 않음
        f(0);               // 정상
    }   
};

using 선언이 기본 클래스의 대입 연산자를 파생 클래스로 가져올 때, 그 시그니처가 우연히 파생 클래스의 복사 대입 또는 이동 대입 연산자와 일치하는 경우, 해당 연산자는 파생 클래스의 암시적으로 선언된 복사/이동 대입 연산자에 의해 가려집니다. 기본 클래스 생성자를 상속하는 using 선언이 우연히 파생 클래스의 복사/이동 생성자와 일치하는 경우에도 동일하게 적용됩니다 (C++11부터) .

상속 생성자의 의미론(semantics)은 C++11에 대한 결함 보고서 에 의해 소급되어 변경되었습니다. 이전에는 상속 생성자 선언이 파생 클래스에 합성된 생성자 선언 집합을 주입하도록 했으며, 이는 중복된 인자 복사/이동을 초래하고, 일부 형태의 SFINAE와 문제있는 상호작용을 일으키며, 일부 경우 주요 ABI에서 구현 불가능할 수 있었습니다. 이전 컴파일러들은 여전히 이전 의미론을 구현할 수 있습니다.

이전 상속 생성자 의미론

using-declaration 이 정의 중인 클래스의 직접 기반 클래스 생성자를 참조하는 경우 (예: using Base :: Base ; ), 해당 기반 클래스의 생성자는 다음 규칙에 따라 상속됩니다:

1) 후보 상속 생성자(candidate inheriting constructors) 집합은 다음으로 구성됩니다
a) 기반 클래스의 모든 비템플릿 생성자 (생략 부호 매개변수가 있는 경우 생략 후) (since C++14)
b) 기본 인자나 생략 부호 매개변수를 가진 각 생성자에 대해, 생략 부호를 제거하고 인자 목록의 끝에서 기본 인자를 하나씩 생략하여 형성된 모든 생성자 시그니처
c) 기반 클래스의 모든 생성자 템플릿 (생략 부호 매개변수가 있는 경우 생략 후) (since C++14)
d) 기본 인자나 생략 부호를 가진 각 생성자 템플릿에 대해, 생략 부호를 제거하고 인자 목록의 끝에서 기본 인자를 하나씩 생략하여 형성된 모든 생성자 시그니처
2) 기본 생성자나 복사/이동 생성자가 아니고, 그 시그니처가 파생 클래스의 사용자 정의 생성자와 일치하지 않는 모든 후보 상속 생성자는 파생 클래스에서 암시적으로 선언됩니다. 기본 매개변수는 상속되지 않습니다:
struct B1
{
    B1(int);
};
struct D1 : B1
{
    using B1::B1;
    // The set of candidate inherited constructors is 
    // 1. B1(const B1&)
    // 2. B1(B1&&)
    // 3. B1(int)
    // D1 has the following constructors:
    // 1. D1() = delete
    // 2. D1(const D1&) 
    // 3. D1(D1&&)
    // 4. D1(int) <- inherited
};
struct B2
{
    B2(int = 13, int = 42);
};
struct D2 : B2
{
    using B2::B2;
    // The set of candidate inherited constructors is
    // 1. B2(const B2&)
    // 2. B2(B2&&)
    // 3. B2(int = 13, int = 42)
    // 4. B2(int = 13)
    // 5. B2()
    // D2 has the following constructors:
    // 1. D2()
    // 2. D2(const D2&)
    // 3. D2(D2&&)
    // 4. D2(int, int) <- inherited
    // 5. D2(int) <- inherited
};

상속된 생성자는 빈 본문과 단일 nested-name-specifier 로 구성된 멤버 초기화 목록 을 가지며, 모든 인자를 기반 클래스 생성자로 전달하는 사용자 정의 생성자와 동등합니다.

이는 해당 기반 생성자와 동일한 접근 권한 을 가집니다. 사용자 정의 생성자가 constexpr 생성자 요구사항을 충족했을 경우 constexpr 입니다. 해당 기반 생성자가 삭제되었거나 기본값 기본 생성자가 삭제되었을 경우(단, 상속되는 생성자의 기반 클래스 생성은 제외) 삭제됩니다. 상속 생성자는 명시적으로 인스턴스화하거나 명시적으로 특수화할 수 없습니다.

두 using-declaration이 동일한 시그니처의 생성자를 상속하는 경우(두 직접 기반 클래스로부터), 프로그램은 잘못된 형식입니다.

상속 생성자 템플릿은 명시적으로 인스턴스화 하거나 명시적으로 특수화 해서는 안 됩니다.

(since C++11)

Pack expansions in using-declarations make it possible to form a class that exposes overloaded members of variadic bases without recursion:

template<typename... Ts>
struct Overloader : Ts...
{
    using Ts::operator()...; // exposes operator() from every base
};
template<typename... T>
Overloader(T...) -> Overloader<T...>; // C++17 deduction guide, not needed in C++20
int main()
{
    auto o = Overloader{ [] (auto const& a) {std::cout << a;},
                         [] (float f) {std::cout << std::setprecision(3) << f;} };
}
(C++17부터)
기능 테스트 매크로 표준 기능
__cpp_inheriting_constructors 200802L (C++11) 생성자 상속
201511L (C++17)
(DR11)
생성자 상속 재정의
__cpp_variadic_using 201611L (C++17) 패킷 확장 in using -선언문

키워드

using

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 258 C++98 파생 클래스의 비상수 멤버 함수가 기본 클래스의
상수 멤버 함수를 재정의 및/또는 숨길 수 있음
재정의와 숨김에는 cv-한정자도
동일해야 함
CWG 1738 C++11 상속 생성자 템플릿의 특수화를 명시적으로
인스턴스화하거나 명시적으로 특수화하는 것이
허용되는지 명확하지 않았음
금지됨
CWG 2504 C++11 가상 기본 클래스에서 상속 생성자의
동작이 불명확했음
명확히 규정됨
P0136R1 C++11 상속 생성자 선언이 파생 클래스에
추가 생성자를 주입함
기본 클래스 생성자가 이름 탐색에
의해 발견되도록 함

참조문헌

  • C++23 표준 (ISO/IEC 14882:2024):
  • 9.9 using 선언 [namespace.udecl]
  • C++20 표준(ISO/IEC 14882:2020):
  • 9.9 using 선언 [namespace.udecl]
  • C++17 표준(ISO/IEC 14882:2017):
  • 10.3.3 using 선언 [namespace.udecl]
  • C++14 표준(ISO/IEC 14882:2014):
  • 7.3.3 using 선언 [namespace.udecl]
  • C++11 표준 (ISO/IEC 14882:2011):
  • 7.3.3 using 선언 [namespace.udecl]
  • C++03 표준(ISO/IEC 14882:2003):
  • 7.3.3 using 선언 [namespace.udecl]
  • C++98 표준(ISO/IEC 14882:1998):
  • 7.3.3 using 선언 [namespace.udecl]