생성자에서 템플릿 매개 변수를 추론하지 않는 이유는 무엇입니까?


102

오늘 내 질문은 매우 간단합니다. 컴파일러가 함수 매개 변수에서 할 수있는 것처럼 클래스 생성자에서 템플릿 매개 변수를 추론 할 수없는 이유는 무엇입니까? 예를 들어, 다음 코드가 유효하지 않은 이유는 무엇입니까?

template<typename obj>
class Variable {
      obj data;
      public: Variable(obj d)
              {
                   data = d;
              }
};

int main()
{
    int num = 2;
    Variable var(num); //would be equivalent to Variable<int> var(num),
    return 0;          //but actually a compile error
}

내가 말했듯이 이것이 유효하지 않다는 것을 이해하므로 내 질문은 그렇지 않습니까? 이것이 큰 통사론 적 허점을 만들까요? 이 기능을 원하지 않는 경우가 있습니까 (유형을 추론하면 문제가 발생하는 경우)? 함수에 대한 템플릿 추론을 허용하는 논리를 이해하려고 노력하고 있지만 적절하게 구성된 클래스에는 적용되지 않습니다.


나는 누군가를 초대하고 (나는 지금 당장은하지 않는다) Drahakar와 Pitis의 대답을 (적어도) 왜 그것이 작동하지 않는지에 대한 좋은 반례로 컴파일하도록 초대 할 것이다
jpinto3912

2
또한이 쉽게를 통해 해결할되어 있습니다template<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
오리 음매

3
원하는 것을 얻을 수 있습니다. var = Variable <decltype (n)> (n);
QuentinUK

18
C ++ 17은 이것을 허용합니다! 이 제안은 수락되었습니다 : open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
underscore_d

1
@underscore_d 훌륭합니다! 시간! 그것이 작동해야하는 방식이고 그렇지 않은 자극의 근원이 제게 자연스럽게 느껴졌습니다.
amdn

답변:


46

생성자가 항상 클래스의 유일한 진입 지점이 아니기 때문에 유효하지 않다고 생각합니다 (복사 생성자와 operator =에 대해 이야기하고 있습니다). 따라서 다음과 같이 클래스를 사용한다고 가정하십시오.

MyClass m(string s);
MyClass *pm;
*pm = m;

파서가 어떤 템플릿 유형이 MyClass pm인지 아는 것이 그렇게 명백한 지 확실하지 않습니다.

내가 말한 내용이 의미가 있는지 확실하지 않지만 의견을 자유롭게 추가 할 수 있습니다. 흥미로운 질문입니다.

C ++ 17

C ++ 17은 생성자 인수에서 유형 추론을 할 수 있습니다.

예 :

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

허용 된 종이 .


8
이것은 실제로 내가 고려하지 않은 좋은 점입니다. 포인터가 특정 유형이어야한다는 사실에 대해서는 전혀 알 수 없습니다 (즉, MyClass <string> * pm이어야 함). 그럴 경우 인스턴스화 할 때 유형을 지정하지 않아도됩니다. 추가 작업의 몇 가지 단순한 문자 (위에 따라 힙이 아닌 스택에 객체가 생성 된 경우에만). 나는 항상 클래스 추론이 웜의 통사론 적 캔을 열 수 있다고 생각했고, 이것이 그럴 수도 있다고 생각합니다.
GRB 2009-06-12

2
생성자에서 템플릿 매개 변수 추론을 허용하려면 두 번째 줄에서와 같이 생성자 호출 없이 비 특수 선언 허용해야하는 방법을 잘 모르겠습니다 . 즉, MyClass *pm선언 된 함수가 template <typename T> void foo();명시 적 전문화없이 호출 될 수없는 것과 같은 이유로 유효하지 않습니다.
Kyle Strand

3
@KyleStrand 예, ' [생성자를 사용하지 않는 예제] ' 때문에 클래스 템플릿 인수를 생성자에서 추론 할 수 없다고 말하면 이 대답은 완전히 관련이 없습니다. 나는 그것이 받아 들여졌고, +29에 도달했고, 누군가가 눈에 띄는 문제를 알아 차리는 데 6 년이 걸렸고, 7 년 동안 단 한 번의 반대표없이 앉아 있었다는 사실을 믿을 수 없습니다. 읽는 동안 아무도 생각하지 않습니까?
underscore_d

1
@underscore_d 현재이 답변은 "이 제안에 문제가있을 수 있습니다. 방금 말한 내용이 의미가 있는지 (!) 모르겠습니다. 그런데 이것은 C ++ 17이 작동하는 방식과 거의 동일합니다. "
Kyle Strand

1
@KyleStrand 아 예, 그것은 또 다른 문제입니다. 제가 알아 차 렸지만 다른 모든 재미 중에서 언급하는 것을 잊었습니다. C ++ 17에 대한 편집은 OP가 아니었고 IMO는 승인되지 않았어야했지만 새로운 답변으로 게시되었습니다. 게시물이 있었더라도 '게시물의 의미 변경'으로 거부 할 수 있었을 것입니다. 시작하기에는 의미가 없었습니다 ... 완전히 새로운 섹션의 편집이 공정한 게임이라는 것을 몰랐고 확실히 덜 과감한 편집이 거부되었지만, 그것이 어떤 리뷰어를 받는지에 대한 추첨의 행운이라고 생각합니다.
underscore_d

27

다른 사람들이 언급 한 이유 때문에 요청한 것을 할 수는 없지만 다음과 같이 할 수 있습니다.

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}

모든 의도와 목적을 위해 요청하는 것과 동일합니다. 캡슐화를 좋아한다면 make_variable을 정적 멤버 함수로 만들 수 있습니다. 이것이 사람들이 명명 된 생성자라고 부르는 것입니다. 따라서 원하는 작업을 수행 할뿐만 아니라 원하는 작업을 거의 수행 할 수 있습니다. 컴파일러는 (명명 된) 생성자에서 템플릿 매개 변수를 유추합니다.

주의 : 합리적인 컴파일러는 다음과 같이 작성할 때 임시 객체를 최적화합니다.

auto v = make_variable(instance);

6
그런 경우 함수를 정적 멤버로 만드는 것이 특별히 유용하지 않다는 점을 지적하고 싶습니다. 그 이유는 어쨌든 클래스가 그것을 호출하기 위해 템플릿 인수를 지정해야하기 때문에 추론 할 필요가 없기 때문입니다.
Predelnik 2014

3
그리고 더 나은 11 ++ C에서 당신이 할 수있는 auto v = make_variable(instance)당신이 실제로 유형을 지정하지 않아도
Claudiu

1
네, static멤버 로서 make 함수를 선언한다는 생각에 롤 ... 잠깐만 생각해보세요. 그 외에 : 무료 메이크업 기능은 참으로했다 솔루션,하지만 그것은, 당신은 단지 당신이있는 거 입력하는 동안 것으로, 중복 보일러의 많은입니다 알고 컴파일러는 당신이 반복하고있는 정보 모두에 액세스 할 수 있기 때문에 필요가 없습니다. .. 고맙게도 C ++ 17은이를 캐노 니즈합니다.
underscore_d

21

이 질문이 제기 된 이후 두 가지 새로운 표준과 곧 새로운 표준이있는 2016 년의 계몽 된 시대에 알아야 할 중요한 것은 C ++ 17 표준을 지원 하는 컴파일러가 코드를 그대로 컴파일 한다는 것 입니다. .

C ++ 17의 클래스 템플릿에 대한 템플릿 인수 추론

여기 (수락 된 답변에 대한 Olzhas Zhumabek의 편집에 의함)는 표준에 대한 관련 변경 사항을 자세히 설명하는 문서입니다.

다른 답변의 문제 해결

현재 최고 등급 답변

이 답변은 "복사 생성자 및 operator="가 올바른 템플릿 전문화를 알지 못함을 나타냅니다.

표준 복사 생성자 가 알려진 템플릿 유형에 operator= 대해서만 존재 하기 때문에 이것은 말도 안됩니다 .

template <typename T>
class MyClass {
    MyClass(const MyClass&) =default;
    ... etc...
};

// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm;   // WHAT IS THIS?
*pm = m;

내가 코멘트에서 언급 한 바와 같이 여기,이없는 이유 에 대한 MyClass *pm또는 추론의 새로운 형태없이 법적 선언 할 수는 : MyClass 유형없는 이의 포인터를 선언하는 이해가되지 않도록, (그것은 템플릿입니다) 유형 MyClass. 다음은 예를 수정할 수있는 한 가지 방법입니다.

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

여기서, pm이다 이미 추론이 사소한 올바른 유형의, 그리고 있도록. 또한 복사 생성자를 호출 할 때 실수로 유형을 혼합하는 것은 불가능합니다 .

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));

여기 pm에의 복사본에 대한 포인터가 있습니다 m. 여기서, MyClass복사 - 구성되는 m- 어떤 유형이다 MyClass<string>(그리고 되지 존재하지 않는 타입 MyClass). 따라서, 지점 pm의 유형을 유추됩니다, 거기 이다 의 템플릿 형 있음을 알 수있는 충분한 정보 m, 따라서의 템플릿 유형 pm이다가 string.

또한 다음은 항상 컴파일 오류를 발생시킵니다 .

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

복사 생성자의 선언이 있기 때문입니다 하지 템플릿 :

MyClass(const MyClass&);

여기서 copy-constructor 인자의 template-type 전체 클래스 의 template-type 과 일치 합니다. 즉이 경우, MyClass<string>인스턴스화, MyClass<string>::MyClass(const MyClass<string>&);그것을 인스턴스화되고, 때 MyClass<int>인스턴스화, MyClass<int>::MyClass(const MyClass<int>&);인스턴스화됩니다. 명시 적으로 지정되거나 템플릿 화 된 생성자가 선언되지 않는 한 컴파일러가을 인스턴스화 할 이유가 없으며 MyClass<int>::MyClass(const MyClass<string>&);이는 분명히 부적절합니다.

Cătălin Pitiș의 답변

Pitiș는 및를 추론하는 예제를 제공하고 다음 Variable<int>Variable<double>같이 말합니다.

두 가지 유형 (Variable 및 Variable)의 코드에 동일한 유형 이름 (Variable)이 있습니다. 내 주관적인 관점에서 볼 때 코드의 가독성에 거의 영향을 미칩니다.

이전 예제에서 언급했듯이 , 새로운 기능이 구문 상 하나처럼 보이더라도 Variable그 자체는 유형 이름 이 아닙니다 .

그런 다음 Pitiș는 적절한 추론을 허용하는 생성자가 제공되지 않으면 어떻게 될 것인지 묻습니다. 대답은 추론이 생성자 호출 에 의해 트리거되기 때문에 추론이 허용되지 않는다는 것입니다 . 생성자 호출이 없으면 추론없습니다 .

이것은 foo여기서 추론 된 버전을 묻는 것과 유사합니다 .

template <typename T> foo();
foo();

대답은 명시된 이유 때문에이 코드가 불법이라는 것입니다.

MSalter의 답변

이것이 제가 말할 수있는 한, 제안 된 기능에 대한 합법적 인 우려를 제기하는 유일한 대답입니다.

예는 다음과 같습니다.

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

핵심 질문은 컴파일러가 여기서 유형 유추 생성자를 선택 합니까 아니면 복사 생성자를 선택합니까?

코드를 시도해 보면 복사 생성자가 선택되었음을 알 수 있습니다. 예제를 확장하려면 :

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

나는 제안과 표준의 새 버전이 이것을 어떻게 명시하고 있는지 잘 모르겠습니다. 그것은 내가 아직 이해하지 못하는 새로운 표준 인 "추론 가이드"에 의해 결정된 것 같습니다.

나는 또한 var4공제가 왜 불법 인지 잘 모르겠습니다 . g ++의 컴파일러 오류는 명령문이 함수 선언으로 구문 분석되고 있음을 나타내는 것 같습니다.


얼마나 훌륭하고 상세한 대답입니까! var4"가장 성가신 구문 분석"의 경우 일뿐입니다 (템플릿 인수 추론과 관련이 없음). 우리는 이것을 위해 여분의 괄호를 사용했지만 요즘에는 중괄호를 사용하여 구조를 명확하게 나타내는 것이 일반적인 조언이라고 생각합니다.
Sumudu Fernando

@SumuduFernando 감사합니다! 그것이 Variable var4(Variable(num));함수 선언으로 취급 된다는 뜻 입니까? 그렇다면 왜 Variable(num)유효한 매개 변수 사양입니까?
Kyle Strand

: @SumuduFernando 결코 마음, 나는이 유효 아무 생각이 없었다 coliru.stacked-crooked.com/a/98c36b8082660941
카일 해변

11

여전히 누락 됨 : 다음 코드를 매우 모호하게 만듭니다.

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

또 다른 좋은 점. Variable (Variable <obj> d)로 정의 된 복사 생성자가 있다고 가정하면 어떤 종류의 우선 순위가 설정되어야합니다.
GRB

1
또는 Pitis의 답변과 관련하여 제안한 것처럼 컴파일러가 정의되지 않은 템플릿 매개 변수 오류를 다시 던지도록하십시오. 그러나 그 길을 택하면 문제 (오류)없이 추론이 발생할 수있는 횟수가 점점 줄어들고 있습니다.
GRB

이것은 실제로 흥미로운 점이며 (내 답변에서 언급했듯이) 수용된 C ++ 17 제안이 어떻게 이것을 해결하는지 아직 확실하지 않습니다.
Kyle Strand

9

컴파일러가 요청한 것을 지원한다고 가정합니다. 그러면이 코드가 유효합니다.

Variable v1( 10); // Variable<int>

// Some code here

Variable v2( 20.4); // Variable<double>

이제 두 가지 유형 (Variable 및 Variable)에 대한 코드에 동일한 유형 이름 (Variable)이 있습니다. 내 주관적인 관점에서 볼 때 코드의 가독성에 거의 영향을 미칩니다. 동일한 네임 스페이스에서 두 개의 다른 유형에 대해 동일한 유형 이름을 갖는 것은 오해의 소지가있는 것 같습니다.

나중에 업데이트 : 고려해야 할 또 다른 사항 : 부분 (또는 전체) 템플릿 전문화입니다.

내가 Variable을 전문화하고 예상 한 생성자를 제공하지 않으면 어떻게됩니까?

그래서 나는 :

template<>
class Variable<int>
{
// Provide default constructor only.
};

그런 다음 코드가 있습니다.

Variable v( 10);

컴파일러는 무엇을해야합니까? 일반 변수 클래스 정의를 사용하여 그것이 변수임을 추론 한 다음 변수가 하나의 매개 변수 생성자를 제공하지 않는다는 것을 발견합니까?


1
더 나쁜 것은 Variable <int> :: Variable (float) 만 있으면 어떨까요? 이제 두 가지 방법으로 Variable (1f)를 추론하고 Variable (1)을 추론 할 수 없습니다.
MSalters

좋은 점이지만 캐스팅으로 쉽게 능가 할 수 있습니다. Variable v1 ((double) 10)
jpinto3912

나는 코드 가독성이 주관적인 문제라는 데 동의하지만 템플릿 전문화에 대해 당신이 말하는 것에 100 % 동의합니다. 해결책은 아마도 정의되지 않은 템플릿 매개 변수 오류를 제공하는 것입니다 (컴파일러가 <int> 전문화를보고 유효한 생성자를 보지 못하면 어떤 템플릿을 사용하고 명시 적으로 지정해야하는지 알 수 없음). 나는 그것이 예쁜 해결책이 아니라는 데 동의합니다. 나는 이것을 처리해야 할 또 다른 주요 구문 구멍으로 추가 할 것입니다 (그러나 결과를 받아들이면 해결할 수 있습니다).
GRB

4
@ jpinto3912-요점을 놓치고 있습니다. 컴파일러는 모든 가능한 Variable <T>를 인스턴스화하여 모든 ctor Variable <T> :: Variable이 모호한 ctor를 제공하는지 확인해야합니다. 모호함을 제거하는 것이 문제가 아닙니다. 원하는 경우 Variable <double>을 직접 인스턴스화하십시오. 그것은 불가능하게 만드는 애초에 모호함을 찾는 것입니다.
MSalters 2009-06-12

6

C ++ 03 및 C ++ 11 표준은 생성자에게 전달 된 매개 변수에서 템플릿 인수 추론을 허용하지 않습니다.

그러나 "생성자를위한 템플릿 매개 변수 추론"에 대한 제안이 있으므로 곧 원하는 것을 얻을 수 있습니다. 편집 : 실제로이 기능은 C ++ 17에서 확인되었습니다.

참조 : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.htmlhttp://www.open-std.org/jtc1/sc22/wg21/docs/ papers / 2015 / p0091r0.html


이 기능은 C ++ 17에 추가되었지만 "곧"이 6-8 년 기간에 적용되는 경우에는 그렇지 않습니다. ;)
ChetS

2

많은 클래스가 생성자 매개 변수에 의존하지 않습니다. 생성자가 하나만 있고이 생성자의 유형에 따라 매개 변수화하는 클래스는 몇 개뿐입니다.

템플릿 추론이 정말 필요한 경우 도우미 함수를 사용하세요.

template<typename obj>
class Variable 
{
      obj data;
public: 
      Variable(obj d)
      : data(d)
      { }
};

template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
    return Variable<obj>(d);
}

1
물론이 기능은 일부 클래스에만 유용하지만 함수 추론에 대해서도 마찬가지입니다. 모든 템플릿 함수가 인수 목록에서 매개 변수를 가져 오는 것은 아니지만 해당 함수에 대한 추론을 허용합니다.
GRB

1

유형 추론은 현재 C ++의 템플릿 함수로 제한되지만 다른 컨텍스트에서 유형 추론이 매우 유용 할 것이라는 사실은 오랫동안 인식되어 왔습니다. 따라서 C ++ 0x의 auto.

동안 정확히 무엇을, C ++ 0X 당신이 아주 가까이 얻을 수있는 다음과 같은 프로그램 할 수 없습니다 제안 :

template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
    // remove reference required for the case that x is an lvalue
    return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}

void test()
{
    auto v = MakeVariable(2); // v is of type Variable<int>
}

0

당신은 컴파일러가 쉽게 추측 할 수 있지만, 내가 아는 한 표준 또는 C ++ 0x에 있지 않으므로 컴파일러 제공자가이 기능을 추가하기 전에 최소 10 년 (ISO 표준 고정 회전율)을 기다려야합니다.


다가오는 표준 auto 키워드가 도입되는 것은 맞지 않습니다. 이 스레드에서 James Hopkins의 게시물을 살펴보십시오. stackoverflow.com/questions/984394/... . 그는 그것이 C ++ 0x에서 어떻게 가능할 것인지 보여줍니다.
ovanes 2009.06.12

1
자신을 바로 잡기 위해 auto 키워드도 현재 표준에 있지만 다른 목적으로 사용됩니다.
ovanes 2009-06-12

(이 답변의 시점에서) 8 년이 될 것 같습니다 ... 그동안 두 가지 기준이 있었음에도 불구하고 10 년은 나쁜 추측이 아닙니다!
Kyle Strand

-1

모두가 익숙해야하는 클래스 인 std :: vector를 참조하여 문제를 살펴 보겠습니다.

첫째, 벡터의 매우 일반적인 용도는 매개 변수가없는 생성자를 사용하는 것입니다.

vector <int> v;

이 경우에는 추론을 수행 할 수 없습니다.

두 번째 일반적인 용도는 미리 크기가 지정된 벡터를 만드는 것입니다.

vector <string> v(100);

여기에서 추론이 사용 된 경우 :

vector v(100);

우리는 문자열이 아닌 정수 벡터를 얻습니다.

마지막으로 "추론"을 사용하여 여러 매개 변수를 사용하는 생성자를 고려하십시오.

vector v( 100, foobar() );      // foobar is some class

추론에 어떤 매개 변수를 사용해야합니까? 두 번째가되어야한다고 컴파일러에게 알려주는 방법이 필요합니다.

벡터처럼 단순한 클래스에 대한 이러한 모든 문제로 인해 추론이 사용되지 않는 이유를 쉽게 알 수 있습니다.


3
나는 당신이 아이디어를 오해하고 있다고 생각합니다. 생성자에 대한 유형 유추는 템플릿 유형이 생성자의 일부인 경우에만 발생합니다. 벡터에 템플릿 정의 template <typename T>가 있다고 가정합니다. 벡터의 생성자는 vector (T size)가 아닌 vector (int size)로 정의되므로 귀하의 예제는 문제가되지 않습니다. vector (T size)의 경우에만 추론이 발생합니다. 첫 번째 예에서 컴파일러는 T가 정의되지 않았다는 오류를 표시합니다. 함수 템플릿 추론이 작동하는 방식과 본질적으로 동일합니다.
GRB

따라서 단일 매개 변수가 있고 해당 매개 변수가 템플릿 매개 변수 유형 인 생성자에 대해서만 발생합니까? 그것은 사라질 정도로 적은 수의 인스턴스처럼 보입니다.

반드시 단일 매개 변수 일 필요는 없습니다. 예를 들어, vector (int size, T firstElement)의 벡터 생성자를 가질 수 있습니다. 템플릿에 여러 매개 변수 (template <typename T, typename U>)가있는 경우 하나는 Holder :: Holder (T firstObject, U secondObject)를 가질 수 있습니다. 템플릿에 여러 매개 변수가 있지만 생성자가 그중 하나만 사용하는 경우 (예 : Holder (U secondObject)) T는 항상 명시 적으로 지정되어야합니다. 규칙은 가능한 한 함수 템플릿 추론과 유사하도록 고안되었습니다.
GRB

-2

ctor를 템플릿으로 만들기 변수는 하나의 형식 만 가질 수 있지만 다양한 ctor를 가질 수 있습니다 .

class Variable {
      obj data; // let the compiler guess
      public:
      template<typename obj>
      Variable(obj d)
       {
           data = d;
       }
};

int main()
{
    int num = 2;
    Variable var(num);  // Variable::data int?

    float num2 = 2.0f;
    Variable var2(num2);  // Variable::data float?
    return 0;         
}

보다? Variable :: data 멤버를 여러 개 가질 수 없습니다.


어떤 시나리오에서도 말이되지 않습니다. obj 데이터 측면에서 obj는 해당 클래스가 더 이상 템플릿이 아니므로 정의되지 않았습니다. 이러한 코드는 어느 쪽이든 유효하지 않습니다.
GRB

나는 당신이 설명하는 컴파일러 동작을 원했기 때문에 (제 경우에는) 그 제한을 우회하는 방법을 알아 냈습니다. (제 경우에는) 흥미로울 수 있습니다. stackoverflow.com/questions/228620/garbage-collection-in-c-why/…
Nick Dandoulakis

-2

이에 대한 자세한 내용 은 C ++ 템플릿 인수 추론 을 참조하십시오.


4
나는 전에이 기사를 읽었고 내가 말하는 것에 대해 많이 이야기하지 않는 것 같았다. 작가가 수업과 관련하여 논증 추론에 대해 이야기하는 것처럼 보이는 유일한 경우는 그가 기사 상단에서 할 수 없다고 말했을 때입니다.;)-만약 당신이 내가 관련이 있다고 생각하는 부분을 지적 할 수 있다면 정말 감사합니다.
GRB
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.