C ++ 14 자동 반환 유형 공제는 언제 사용해야합니까?


144

GCC 4.8.0이 출시되면 C ++ 14의 일부인 자동 반환 유형 공제를 지원하는 컴파일러가 있습니다. 를 사용하면 다음 -std=c++1y과 같이 할 수 있습니다.

auto foo() { //deduced to be int
    return 5;
}

내 질문은 :이 기능을 언제 사용해야합니까? 언제 그리고 언제 코드를 더 깨끗하게해야합니까?

시나리오 1

내가 생각할 수있는 첫 번째 시나리오는 가능할 때마다입니다. 이런 식으로 작성할 수있는 모든 기능이 있어야합니다. 이것의 문제점은 코드를 항상 더 읽기 쉽게 만들 수는 없다는 것입니다.

시나리오 2

다음 시나리오는 더 복잡한 반환 유형을 피하는 것입니다. 매우 가벼운 예로서 :

template<typename T, typename U>
auto add(T t, U u) { //almost deduced as decltype(t + u): decltype(auto) would
    return t + u;
}

나는 그것이 실제로 문제가 될 것이라고는 생각하지 않지만, 반환 유형이 매개 변수에 명시 적으로 의존하는 것이 어떤 경우에는 더 명확 할 수 있다고 생각합니다.

시나리오 3

다음으로 중복을 방지하려면

auto foo() {
    std::vector<std::map<std::pair<int, double>, int>> ret;
    //fill ret in with stuff
    return ret;
}

C ++ 11에서는 때로는 return {5, 6, 7};벡터 대신에 항상 작동 할 수는 있지만 항상 작동하지는 않으며 함수 헤더와 함수 본문 모두에서 유형을 지정해야합니다. 이는 순전히 중복이며, 자동 반환 유형 공제는 이러한 중복을 방지합니다.

시나리오 4

마지막으로 매우 간단한 함수 대신 사용할 수 있습니다.

auto position() {
    return pos_;
}

auto area() {
    return length_ * width_;
}

그러나 때로는 정확한 유형을 알고 싶을 때 함수를 볼 수 있으며, 제공되지 않으면 코드의 다른 지점으로 이동해야합니다 pos_.

결론

이러한 시나리오를 제시하면 실제로이 기능이 코드를 더 깨끗하게 만드는 데 유용한 상황은 무엇입니까? 여기서 언급하지 않은 시나리오는 어떻습니까? 나중에 물지 않도록이 기능을 사용하기 전에 어떤 예방 조치를 취해야합니까? 이 기능이 없으면 불가능한 새로운 기능이 있습니까?

여러 질문은 이에 대한 답을 찾는 관점을 찾는 데 도움을주기위한 것입니다.


18
멋진 질문입니다! 어떤 시나리오에서 코드를 "더 나은"상태로 만들 것인지 묻는 동안, 어떤 시나리오로 인해 더 나빠질 지 궁금합니다 .
Drew Dormann

2
@DrewDormann, 그게 궁금합니다. 새로운 기능을 사용하고 싶지만 언제 사용하고 언제 사용하지 않는지를 아는 것이 매우 중요합니다. 우리가 이것을 알아 내기 위해 취하는 새로운 기능이 생겨날 시간이 있으므로 공식적으로 올 때를 대비할 수 있도록 지금 해보자. :)
chris

2
@NicolBolas, 아마도 컴파일러의 실제 릴리스에 있다는 사실은 사람들이 개인 코드로 사용하기에 충분할 것입니다 (이 시점에서 프로젝트에서 멀리 떨어져 있어야합니다). 나는 내 코드에서 최신 기능을 사용하는 것을 좋아하는 사람들 중 하나이며 제안이위원회와 얼마나 잘 진행되고 있는지 알지 못하지만이 새로운 옵션에 처음으로 포함 된 사실을 알았습니다. 어떤 것. 나중에 나올 것이 더 좋을지, 또는 그것이 올 것이라고 확신 할 때 (나는 그것이 얼마나 잘 작동 할 지 모르겠습니다) 부활했을 것입니다.
chris

1
@NicolBolas, 그것이 도움이된다면, 지금 채택되었습니다 : p
chris

1
현재 답변에 따르면 ->decltype(t+u)자동 공제로 대체 하면 SFINAE가 사망한다고 언급하지 않는 것 같습니다 .
Marc Glisse

답변:


62

C ++ 11은 람다에서 반환 유형 공제를 사용할 때와 auto변수 를 사용할 때와 비슷한 질문을 제기 합니다.

C와 C ++ 03의 질문에 대한 기존의 대답은 "표현 경계를 넘어서 명시 적으로 표현하는 것입니다. 표현식 내에서 일반적으로 암시 적이지만 캐스트로 명시 적으로 만들 수 있습니다". C ++ 11 및 C ++ 1y는 유형 공제 도구를 도입하여 새로운 장소에서 유형을 생략 할 수 있습니다.

죄송 합니다만 일반적인 규칙을 적용하여이 문제를 미리 해결할 수는 없습니다. 특정 코드를 살펴보고 어디에서나 유형을 지정하는 가독성에 도움이되는지 여부를 스스로 결정해야합니다. 코드에서 "이 유형은 X"라고 말하는 것이 더 낫거나 더 낫습니다. "이것의 타입은 코드의이 부분을 이해하는 것과 무관하다 : 컴파일러는 알아야하고 우리는 아마 그것을 해결할 수는 있지만 여기서 말할 필요는 없다"고 말하는가?

"가독성"은 객관적으로 정의되지 않았으며 [*] 독자에 따라 다양하기 때문에 스타일 가이드가 전적으로 만족할 수없는 코드의 작성자 / 편집자로서 책임을 져야합니다. 스타일 가이드가 규범을 규정하는 정도까지도, 다른 사람들은 다른 규범을 선호하고 "읽기 어려운"것으로 생소한 것을 발견하는 경향이 있습니다. 따라서 특정 제안 된 스타일 규칙의 가독성은 종종 다른 스타일 규칙의 맥락에서만 판단 할 수 있습니다.

모든 시나리오 (첫 번째조차도)는 누군가의 코딩 스타일에 사용됩니다. 개인적으로 나는 두 번째가 가장 강력한 유스 케이스라고 생각하지만 문서 도구에 따라 달라질 것으로 예상합니다. 함수 템플릿의 반환 유형이로 auto문서화되어 있지만 문서화 된 것으로보고하면 decltype(t+u)(희망적으로) 신뢰할 수있는 게시 된 인터페이스를 만드는 것으로 문서화 된 것을 보는 것이 별로 도움이되지 않습니다 .

[*] 때때로 누군가가 객관적인 측정을 시도합니다. 누구나 통계적으로 유의하고 일반적으로 적용 가능한 결과를 내놓을 정도의 적은 정도로, "읽을 수있는"것에 대한 저자의 본능에 찬성하여 일하는 프로그래머에 의해 완전히 무시됩니다.


1
람다와의 연결에 대한 좋은 지적 (더 복잡한 함수 몸체를 허용하지만). 가장 중요한 것은 새로운 기능이며 여전히 각 사용 사례의 장단점을 조정하려고합니다. 이렇게하려면 왜 내가 무엇을 선호하는지 알아낼 수 있도록 왜 사용되는지에 대한 이유를 알아 보는 것이 도움이됩니다. 나는 그것을 너무나 생각하고 있을지도 모르지만, 그것이 바로 그 방법입니다.
chris

1
@ chris : 내가 말했듯이, 나는 그것이 모두 똑같은 것이라고 생각합니다. 코드가 "이것의 타입은 X이다"라고 말하는 것이 좋을까, 또는 코드가 "이것의 타입은 관련이 없으며 컴파일러가 알아야하고 우리는 아마 그것을 해결할 수있다"고 말하는 것이 더 낫다 그러나 우리는 그것을 말할 필요가 없습니다. " 우리가 글을 쓸 때 우리 1.0 + 27U는 후자를 주장하고, 글을 쓸 때 우리 (double)1.0 + (double)27U는 전자를 주장합니다. 기능의 단순성, 복제 정도, 회피 decltype는 모두 그에 기여할 수 있지만 확실하게 결정적인 것은 없습니다.
Steve Jessop

1
코드가 "이것의 타입은 X이다"라고 말하는 것이 좋을까, 또는 코드가 "이것의 타입은 관련이 없으며 컴파일러가 알아야하고 우리는 아마 그것을 해결할 수있다"고 말하는 것이 더 낫다 그러나 우리는 그것을 말할 필요가 없습니다. " -그 문장은 내가 찾고있는 것과 정확히 일치합니다. 이 기능을 사용하는 옵션을 전반적으로 살펴볼 때이 점을 고려하겠습니다 auto.
chris

1
IDE가 "가독성 문제"를 완화 할 수 있다고 덧붙이고 싶습니다. Visual Studio를 예로 들어 보겠습니다. auto키워드 위에 마우스를 올리면 실제 반환 유형이 표시됩니다.
andreee

1
@andreee : 그것은 한계 내에 있습니다. 유형에 많은 별칭이있는 경우 실제 유형을 아는 것이 항상 원하는만큼 유용한 것은 아닙니다. 예를 들어 반복자 유형은 int*이지만 실제로 중요한 것은 그것이 현재 빌드 옵션에 int*있기 때문입니다 std::vector<int>::iterator_type!
Steve Jessop

30

일반적으로 함수 반환 유형은 함수를 문서화하는 데 큰 도움이됩니다. 사용자는 예상되는 것을 알 수 있습니다. 그러나 중복을 피하기 위해 반환 유형을 삭제하는 것이 좋을 것이라고 생각되는 경우가 있습니다. 예를 들면 다음과 같습니다.

template<typename F, typename Tuple, int... I>
  auto
  apply_(F&& f, Tuple&& args, int_seq<I...>) ->
  decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...))
  {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
  }

template<typename F, typename Tuple,
         typename Indices = make_int_seq<std::tuple_size<Tuple>::value>>
  auto
  apply(F&& f, Tuple&& args) ->
  decltype(apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices()))
  {
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
  }

이 예는 공식위원회 논문 N3493에서 발췌 한 것 입니다. 함수의 목적은 applya의 요소를 함수 로 전달 std::tuple하고 결과를 반환하는 것입니다. int_seqmake_int_seq구현의 일부분이며, 아마 단지 그것이 무엇을 이해하려고 노력하는 모든 사용자를 혼란스럽게합니다.

보다시피, 리턴 타입은 decltype리턴 된 표현식에 지나지 않습니다 . 또한 apply_사용자가 볼 수있는 것이 아니기 때문에 반환 유형이 apply' 와 같거나 적을 때 문서화하는 것이 유용하다는 것을 확신하지 못합니다 . 이 특별한 경우 반환 유형을 삭제하면 함수를 더 읽기 쉽게 만듭니다. 이 반환 유형은 표준 N3915decltype(auto) 에 추가 apply하기 위해 제안서에서 실제로 삭제되고 대체되었습니다 (또한 원래의 대답은이 문서보다 우선 합니다).

template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
    return forward<F>(f)(get<I>(forward<Tuple>(t))...);
}

template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
    using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>;
    return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{});
}

그러나 대부분의 경우 반환 유형을 유지하는 것이 좋습니다. 위에서 설명한 특정 경우에 반환 유형은 읽을 수 없으며 잠재적 인 사용자는이를 알 수 없습니다. 예제가 포함 된 좋은 문서가 훨씬 유용 할 것입니다.


상태 : 아직 언급되지 않은 또 다른 것은 declype(t+u)사용할 수 있도록 표현 SFINAE을 , decltype(auto)(비록하지 않는 제안이 이 동작을 변경할 수). 예를 들어, foobar유형의 foo멤버 함수가 존재하는 경우이를 호출하거나 유형의 멤버 함수가 존재하는 경우이를 호출 하는 함수 를 예로 들어 bar클래스가 항상 정확 foo하거나 bar한 번에 둘 다를 갖지 않는다고 가정하십시오 .

struct X
{
    void foo() const { std::cout << "foo\n"; }
};

struct Y
{
    void bar() const { std::cout << "bar\n"; }
};

template<typename C> 
auto foobar(const C& c) -> decltype(c.foo())
{
    return c.foo();
}

template<typename C> 
auto foobar(const C& c) -> decltype(c.bar())
{
    return c.bar();
}

호출 foobar의 인스턴스 것은 X표시됩니다 foo호출하는 동안 foobar의 인스턴스에 Y의지 표시 bar. 대신 자동 반환 유형 공제를 사용하면 (또는 포함하지 않음 decltype(auto)) SFINAE 표현식을 얻지 못 하거나 foobar인스턴스를 호출 X하거나 Y컴파일 타임 오류가 발생합니다.


8

절대 필요하지 않습니다. 당신이해야 할 시점에 관해서는 그것에 대해 많은 다른 대답을 얻을 것입니다. 나는 실제로 표준의 일부로 받아 들여지고 대부분의 주요 컴파일러가 같은 방식으로 잘 지원할 때까지 전혀 말하지 않을 것입니다.

그 외에도 종교적인 논쟁이 될 것입니다. 개인적으로 실제 반환 유형을 입력하지 않으면 코드가 더 명확 해지고 유지 보수가 훨씬 쉬워집니다 (함수의 서명을보고 그것이 무엇을 리턴하는지 실제로 코드를 읽어야 함을 알 수 있음). 한 유형을 반환해야한다고 생각하고 컴파일러는 다른 모든 원인을 생각합니다 (내가 사용한 모든 스크립팅 언어에서 발생한 것처럼). 나는 자동차가 큰 실수라고 생각하며, 그것은 도움보다 훨씬 더 많은 고통을 초래할 것입니다. 다른 사람들은 프로그래밍 철학에 따라 항상 사용해야한다고 말합니다. 여하튼, 이것은이 사이트의 범위를 벗어납니다.


1
나는 다른 배경을 가진 사람들이 이것에 대해 다른 의견을 가질 것이라는 데 동의하지만, 이것이 C ++ 같은 결론에 도달하기를 바랍니다. (거의) 모든 경우에 #defines를 사용하여 C ++를 VB로 바꾸는 것과 같이 언어 기능을 남용하여 다른 언어로 만들려고하는 것은 변명의 여지가 없습니다 . 기능은 일반적으로 해당 언어의 프로그래머가 익숙한 것에 따라 언어의 사고 방식에서 올바른 사용법과 그렇지 않은 것에 대한 좋은 합의를 갖습니다. 동일한 기능이 여러 언어로 제공 될 수 있지만 각 기능 사용에 대한 자체 지침이 있습니다.
chris

2
일부 기능에는 좋은 합의가 있습니다. 많은 사람들은 그렇지 않습니다. 나는 Boost의 대다수가 사용해서는 안되는 쓰레기라고 생각하는 많은 프로그래머를 알고 있습니다. 나는 또한 C ++에서 가장 큰 일을 생각하는 사람들을 알고 있습니다. 두 경우 모두 흥미로운 토론이 있다고 생각하지만 실제로는이 사이트에서 건설적이지 않은 옵션의 정확한 예입니다.
Gabe Sechan

2
아니요, 전적으로 당신의 의견에 동의하지 않으며 auto순수한 축복 이라고 생각 합니다. 많은 중복성을 제거합니다. 때때로 리턴 타입을 반복하는 것은 고통입니다. 람다를 반환하려는 경우 결과를에 저장하지 않으면 불가능할 수 있으며 이로 std::function인해 약간의 오버 헤드가 발생할 수 있습니다.
Yongwei Wu

1
모든 것이 필요한 상황이 하나 이상 있습니다. 함수를 호출 한 다음 반환하기 전에 결과를 기록해야하며 함수의 반환 유형이 모두 같을 필요는 없다고 가정합니다. 함수와 인수를 매개 변수로 사용하는 로깅 함수를 통해 수행하는 경우 로깅 함수의 반환 유형을 선언 auto하여 전달 된 함수의 반환 유형과 항상 일치해야합니다.
저스틴 타임-복원 모니카

1
[기술적으로 다른 방법이 있지만, 기본적으로 정확히 같은 일을하기 위해 템플릿 매직을 사용하고 여전히 auto후행 리턴 타입을위한 로깅 기능이 필요합니다 .]
Justin Time-Reinstate Monica

7

함수의 단순 성과는 아무런 관련이 없습니다 ( 이 질문의 삭제 된 복제본 으로 가정).

반환 유형은 고정되어 auto있거나 ( 사용하지 않음 ) 템플릿 매개 변수에 따라 복잡한 방식으로 의존합니다 ( auto대부분의 경우 decltype여러 반환 지점이있을 때 사용).


3

실제 생산 환경을 고려하십시오 : 많은 함수와 단위 테스트는 모두의 반환 유형에 상호 의존적입니다 foo(). 이제 리턴 유형이 어떤 이유로 든 변경되어야한다고 가정하십시오.

반환 유형이 auto어디에나 있고 호출자 foo()및 관련 함수 auto가 반환 된 값을 가져올 때 사용하는 경우 변경해야 할 사항이 최소화됩니다. 그렇지 않다면 이는 매우 지루하고 오류가 발생하기 쉬운 시간을 의미 할 수 있습니다.

실제 예를 들어, 원시 포인터 사용에서 스마트 포인터로 모듈을 변경하라는 요청을 받았습니다. 단위 테스트 수정은 실제 코드보다 더 힘들었습니다.

이것을 처리 할 수있는 다른 방법이 있지만 auto반환 유형을 사용 하는 것이 좋습니다.


1
그런 경우에는 쓰는 것이 낫지 decltype(foo())않습니까?
Oren S

개인적으로, 이 경우, 특히 클래스 범위 일 때 typedefs 및 별칭 선언 (예 : using유형 선언)이 더 좋습니다.
MAChitgarha

3

반환 유형 자동이 완벽한 예를 제공하고 싶습니다.

긴 후속 함수 호출을 위해 짧은 별명을 작성한다고 가정하십시오. auto를 사용하면 원래 반환 유형을 처리 할 필요가 없으며 (향후 변경 될 수 있음) 사용자는 원래 기능을 클릭하여 실제 반환 유형을 얻을 수 있습니다.

inline auto CreateEntity() { return GetContext()->GetEntityManager()->CreateEntity(); }

추신 : 질문에 달려 있습니다 .


2

시나리오 3의 경우 반환되는 로컬 변수를 사용하여 반환 유형의 함수 서명을 설정합니다. 클라이언트 프로그래머가 함수가 반환하는 것을 더 명확하게합니다. 이처럼 :

시나리오 3 중복을 방지하려면

std::vector<std::map<std::pair<int, double>, int>> foo() {
    decltype(foo()) ret;
    return ret;
}

예, 자동 키워드는 없지만 중복을 방지하고 소스에 액세스 할 수없는 프로그래머에게 더 쉬운 시간을 제공하기 위해 교장은 동일합니다.


2
IMO에 대한 더 나은 해결책은로 표현하려는 개념에 대한 도메인 특정 이름을 제공 한 vector<map<pair<int,double>,int>다음 사용하는 것입니다.
davidbak
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.