실제로 오버로드 된 && 및 || 이유가 있습니까? 단락하지 않습니까?


137

짧은 단락의 운영자의 행동 &&과는 ||프로그래머를위한 놀라운 도구입니다.

그러나 오버로드 될 때 왜이 동작이 손실됩니까? 연산자는 함수의 구문 설탕 일 뿐이지 만 연산자 bool는이 동작 을 가지고 있습니다. 왜이 단일 유형으로 제한되어야합니까? 이것 뒤에 기술적 이유가 있습니까?


1
@PiotrS. 그 질문은 아마도 답일 것입니다. 나는 표준 이이 목적을 위해 새로운 구문을 정의 할 수 있다고 생각합니다. 아마 operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)
iFreilicht

1
@PiotrS .: 3 상태 논리를 고려하십시오 {true, false, nil}. nil&& x == nil단락 될 수 있기 때문에 .
MSalters

1
@MSalters : 고려해 봅시다 std::valarray<bool> a, b, c;. 어떻게 a || b || c단락 을 상상 하십니까?
Piotr Skotnicki

4
@PiotrS. : 단락이 의미 가 있는 비 bool 유형이 하나 이상 있다고 주장합니다 . 나는 단락이 모든 비 부울 유형에 적합하다고 주장하지는 않습니다 .
MSalters

3
아직 아무도 언급하지 않았지만 이전 버전과의 호환성 문제도 있습니다. 이 단락이 적용되는 상황을 제한하기 위해 특별한주의를 기울이지 않는 한, 그러한 단락은 평가되는 두 피연산자에 과부하가 걸리 operator&&거나 operator||의존 하는 기존 코드를 손상시킬 수 있습니다. 기존 언어에 기능을 추가 할 때 이전 버전과의 호환성을 유지하는 것이 중요합니다.
David Hammen

답변:


151

모든 설계 프로세스는 서로 호환되지 않는 목표간에 타협을 초래합니다. 불행히도 &&C ++에서 오버로드 된 운영자를 위한 설계 프로세스 는 혼란스러운 최종 결과를 &&낳았습니다. 단락 동작 에서 원하는 기능 은 생략되었습니다.

그 디자인 프로세스에 대한 세부 사항은 내가 알지 못하는 불행한 곳에서 끝났습니다. 그러나 나중의 설계 프로세스가 어떻게이 불쾌한 결과를 고려했는지를 보는 것이 적절합니다. C #에서 오버로드 된 &&작업자 단락되었습니다. C #의 디자이너는 어떻게 그것을 달성 했습니까?

다른 답변 중 하나는 "람다 리프팅"을 제안합니다. 그건:

A && B

도덕적으로 동등한 것으로 실현 될 수 있습니다.

operator_&& ( A, ()=> B )

여기서 두 번째 논증은 평가할 때 부작용과 표현의 가치가 만들어 지도록 게으른 평가를위한 메커니즘을 사용합니다. 오버로드 된 연산자의 구현은 필요한 경우에만 지연 평가를 수행합니다.

이것이 C # 디자인 팀이 한 것이 아닙니다. (옆으로 : 람다 리프팅 연산자 의 표현 트리 표현 을 할 때 내가 한 일이지만 ??, 일부 변환 작업은 느리게 수행해야합니다. 그러나 자세하게 설명하는 것은 큰 혼란이 될 것입니다. 작동하지만 우리가 그것을 피하기에 충분히 무겁습니다.)

오히려 C # 솔루션은 문제를 두 가지 별도의 문제로 나눕니다.

  • 오른쪽 피연산자를 평가해야합니까?
  • 위의 대답이 "예"라면 두 피연산자를 어떻게 결합합니까?

따라서 &&직접 과부하 되는 것을 불법으로하여 문제를 해결 합니다. 오히려 C # 에서는 두 개의 연산자 를 오버로드해야합니다 . 각 연산자는이 두 가지 질문 중 하나에 응답합니다.

class C
{
    // Is this thing "false-ish"? If yes, we can skip computing the right
    // hand size of an &&
    public static bool operator false (C c) { whatever }

    // If we didn't skip the RHS, how do we combine them?
    public static C operator & (C left, C right) { whatever }
    ...

(실제로 세 개. C #에서는 연산자 false가 제공 되면 연산자 도 제공 true해야하며,이 질문에 대한 답은 "진정한가?"라는 질문에 대답해야합니다. 둘 다 필요합니다.)

다음 형식의 진술을 고려하십시오.

C cresult = cleft && cright;

컴파일러는이 의사 C #을 작성했다고 생각한대로 이에 대한 코드를 생성합니다.

C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);

보다시피, 왼쪽은 항상 평가됩니다. "거짓"인 것으로 판단되면 결과입니다. 그렇지 않으면 오른쪽이 평가되고 열성적인 사용자 정의 연산자 &가 호출됩니다.

||오퍼레이터는 오퍼레이터의 호출에 해당하고 싶어 같이 유사한 방식으로 정의된다 |연산자

cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);

네 개의 연산자를 정의하여 - true, false, &|- C #을 당신이 말을뿐만 아니라 수 있습니다 cleft && cright비 단락뿐만 아니라 cleft & cright, 또한 if (cleft) if (cright) ..., 및 c ? consequence : alternativewhile(c), 등등.

이제 모든 설계 프로세스가 타협의 결과라고 말했습니다. 여기서 C # 언어 디자이너는 단락 &&||올바르게 처리했지만 사람 대신 네 명의 연산자를 오버로드해야하므로 일부 사람들은 혼란스러워합니다. 연산자 true / false 기능은 C #에서 가장 잘 이해되지 않은 기능 중 하나입니다. C ++ 사용자에게 친숙하고 직관적 인 언어를 사용한다는 목표는 단락을 원하고 람다 리프팅 또는 다른 형태의 게으른 평가를 구현하지 않으려는 욕구에 반대했습니다. 그게 합리적인 타협 위치라고 생각하지만, 그것을 실현하는 것이 중요 하다 타협 위치입니다. 그냥 다른 C ++의 설계자가 착륙 한 것보다 위치를 타협하십시오.

그러한 연산자에 대한 언어 디자인의 주제에 관심이 있다면 C #이 이러한 연산자를 nullable 부울에 정의하지 않는 이유에 대한 시리즈를 읽으십시오.

http://ericlippert.com/2012/03/26/null-is-not-false-part-one/


1
@Deduplicator :이 질문과 답변을 읽어보실 수도 있습니다 : stackoverflow.com/questions/5965968/…
Eric Lippert

5
이 경우에는 타협이 정당한 것 이상이라고 생각합니다. 복잡한 것은 클래스 라이브러리의 설계자 만 고려해야 할 사항이며, 이러한 합병증 과 함께 라이브러리 소비 를보다 쉽고 직관적으로 만듭니다.
코디 그레이

1
@EricLippert 나는 Envision이 그가이 게시물을보고 그것이 당신이라고 생각했다고 말하고 있다고 생각합니다. 그는 your post무관하다고 말하지 않았습니다 . His noticing your distinct writing style관련이 없습니다.
WernerCD

5
Microsoft 팀은 (1) C #에서 옳은 일을하기 위해 상당한 노력을 기울이고 (2) 그렇지 않은 경우보다 더 많은 시간을 얻는 것에 대해 충분한 신용을 얻지 못합니다.
codenheim

2
@Voo : 당신이 암시 적 변환을 구현하도록 선택하면 bool당신은 사용할 수 있습니다 &&||구현없이 operator true/false또는 operator &/|아무런 문제가 C #으로. 문제는 가능한 변환bool 이 없거나 바람직하지 않은 상황 에서 정확하게 발생 합니다.
Eric Lippert

43

요점은 (C ++ 98의 범위 내에서) 오른쪽 피연산자가 오버로드 된 연산자 함수에 인수로 전달된다는 것입니다. 그렇게 하면 이미 평가 된 것 입니다. 이것을 피할 수 있는 operator||()또는 operator&&()코드가 없거나 할 수있는 것은 없습니다.

원래 연산자는 함수가 아니지만 언어의 하위 수준에서 구현되기 때문에 다릅니다.

추가 언어 기능은 할 수 오른쪽의 비 평가 피연산자 구문 만들었 을 가능 . 그러나 이것은 의미 론적으로 유용한 몇 가지 경우가 있기 때문에 신경 쓰지 않았습니다 . (과 마찬가지로 ? :오버로드에는 사용할 수 없습니다.

(람다를 표준으로 도입하는 데 16 년이 걸렸습니다 ...)

의미 적 사용에 대해서는 다음을 고려하십시오.

objectA && objectB

이것은 다음과 같이 요약됩니다.

template< typename T >
ClassA.operator&&( T const & objectB )

에 대한 변환 연산자를 호출하는 것 외에 objectB (알 수없는 유형)로 정확히 무엇을하고 싶은지 bool, 언어 정의를 위해 단어에 어떻게 넣었는지 생각해보십시오.

그리고 당신 bool로 변환을 호출 한다면 , 음 ...

objectA && obectB

같은 일을합니까, 이제합니까? 왜 처음에 과부하입니까?


7
논리 오류는 현재 정의 된 언어 내에서 다르게 정의 된 언어의 영향에 대해 추론하는 것입니다. 옛날에는 많은 초보자들이 그 일을 했어요. "가상 생성자". 그런 상자 생각에서 벗어나기 위해 엄청난 양의 설명이 필요했습니다. 어쨌든 내장 연산자의 단락으로 인해 인수 비평가에 대한 보장이 있습니다. 단락이 정의 된 경우 사용자 정의 과부하에도 그러한 보증이 적용됩니다.
건배와 hth. -Alf

1
@iFreilicht : 나는 기본적으로 Deduplicator 또는 Piotr과 같은 단어를 다른 단어로 말했습니다. 편집 된 답변에서 요점을 조금 자세히 설명했습니다. 이 방법은 훨씬 더 편리했고, 필요한 언어 확장 (예 : 람다)은 최근까지 존재하지 않았으며 그 혜택은 무시할 수있었습니다. 책임이있는 사람들이 이미하지 않은 것을 "좋아"한 것이 몇 번 수행 1998 년에 다시, 컴파일러 빌더에 의해, 그것은 역효과. (참조 export.)
DevSolar

9
@iFreilicht : bool두 클래스 의 변환 연산자는 모든 멤버 변수에 액세스 할 수 있으며 내장 연산자와 잘 작동합니다. 다른 건 있지만 변환 - 투 - 부울 어쨌든 단락 회로 평가를 위해 의미 이해가되지 않습니다! 구문 적 관점이 아닌 시맨틱 관점에서 이것에 접근하십시오. 어떻게 하려고 하는지 , 어떻게 하려고하지 않습니까 ?
DevSolar

1
나는 내가 생각할 수 없다는 것을 인정해야한다. 단락이 존재하는 유일한 이유는 부울 연산 시간을 절약하고 모든 인수를 평가하기 전에 표현식의 결과를 알 수 있기 때문입니다. 다른 AND 연산으로, 이것은 사실이 아니다, 그 이유 즉 &&&같은 조작이 아니다. 그것을 깨닫게 도와 주셔서 감사합니다.
iFreilicht

8
@iFreilicht : 오히려 단락의 목적은 왼쪽의 계산이 오른쪽의 전제 조건의 진실을 확립 할 수 있기 때문 입니다. if (x != NULL && x->foo)속도가 아니라 안전을 위해 단락이 필요합니다.
Eric Lippert

26

기능은 생각, 설계, 구현, 문서화 및 배송되어야합니다.

이제 우리는 그것을 생각했습니다. 왜 그것이 쉬운 지 (그리고 그렇게하기 어려운 이유)를 보도록합시다. 또한 자원의 양이 제한되어 있으므로 추가하면 다른 항목이 잘릴 수 있습니다 (무엇을 원하십니까?).


이론적으로, 모든 연산자 는 C ++ 11 기준 으로 단 하나의 "사소한" 추가 언어 기능 만으로 단락 동작을 허용 할 수 있습니다 (람다가 도입 된 시점, 1979 년 "클래스가있는 C"가 시작된 지 32 년이 지난 후 람다는 도입 됨) C ++ 98 이후) :

C ++은 필요하고 허용 될 때까지 평가를 피하기 위해 인수를 지연 평가 (숨겨진 람다)로 주석을 달 수있는 방법이 필요합니다 (사전 조건이 충족 됨).


이론적 인 기능은 어떤 모습입니까 (새로운 기능을 광범위하게 사용할 수 있어야 함).

lazy함수 인수에 적용된 주석은 함수를 functor를 기대하는 템플릿으로 만들고 컴파일러가 표현식을 functor로 묶도록합니다.

A operator&&(B b, __lazy C c) {return c;}

// And be called like
exp_b && exp_c;
// or
operator&&(exp_b, exp_c);

다음과 같이 표지 아래에 표시됩니다.

template<class Func> A operator&&(B b, Func& f) {auto&& c = f(); return c;}
// With `f` restricted to no-argument functors returning a `C`.

// And the call:
operator&&(exp_b, [&]{return exp_c;});

람다는 숨겨져 있으며 최대 한 번 호출됩니다. 공통 하위 식 제거 가능성의 감소와는 별도로, 성능 저하
가 없어야합니다 .


구현 복잡성 및 개념적 복잡성 (다른 기능에 대한 복잡성을 충분히 완화하지 않는 한 모든 기능이 모두 증가 함) 외에도 다른 중요한 고려 사항 인 이전 버전과의 호환성을 살펴 보겠습니다.

언어 기능 은 코드를 손상시키지 않지만 코드를 활용하여 API를 미묘하게 변경하므로 기존 라이브러리에서 사용하면 자동 변경이 이루어집니다.

BTW :이 기능은 사용하기는 쉽지만 별도의 정의를 위해 C # 솔루션을 분할 &&하고 ||두 가지 기능 을 사용하는 것보다 엄격 합니다.


6
@iFreilicht : "기능 X가 존재하지 않는 이유는 무엇입니까?" 동일한 답변을 갖습니다. 존재하려면 기능을 생각하고, 좋은 아이디어로 간주하고, 설계, 지정, 구현, 테스트, 문서화하여 최종 사용자에게 제공해야합니다. 이러한 일 중 하나라도 발생하지 않으면 기능이 없습니다. 이러한 기능 중 하나는 제안 된 기능으로 발생하지 않았습니다. 어느 것이 역사적 연구 문제인지 알아 내기; 그 중 하나가 수행되지 않은 것을 신경 쓰면 디자인위원회에서 사람들과 대화를 시작하십시오.
Eric Lippert

1
@EricLippert : 그리고 어떤 이유인지에 따라 구현 될 때까지 반복하십시오. 아마도 너무 복잡하다고 생각하고 아무도 재평가를 할 생각이 없었습니다. 또는 재평가는 이전에 개최 된 것과 다른 이유로 거절해야했습니다. (btw : 귀하의 의견의 요점을 추가했습니다)
중복 제거기

@Deduplicator 표현식 템플릿을 사용하면 지연 키워드 나 람다가 필요하지 않습니다.
Sumant

역사적으로 제쳐두고, 원래 Algol 68 언어는 "프로 시저 링"강압 (deproceduring)을 가졌다는 점에 유의하십시오. 즉, 컨텍스트가 함수 타입이 아닌 결과 타입을 요구할 때 매개 변수가없는 함수를 암시 적으로 호출하는 것을 의미합니다. 이는 "모수없는 함수 반환 T"( 알골 68의 철자 " proc T") 값을 요구하는 위치에서 T 타입의 표현식 이 내재적으로 주어진 표현식 (암시 적 람다)을 리턴하는 함수 본문으로 변환 됨을 의미합니다 . 이 기능은 1973 년 언어 개정판에서 (절차와 달리) 제거되었습니다.
Marc van Leeuwen

... C ++의 경우 비슷한 접근 방식은 &&"T를 리턴하는 함수에 대한 포인터"유형의 인수 하나와 T 유형의 인수 표현식을 내재적으로 람다 표현식으로 변환 할 수있는 추가 변환 규칙을 사용하는 연산자를 선언 할 수 있습니다 . 구문 레벨에서 수행해야하므로 이는 일반적인 변환이 아닙니다. 런타임시 T 유형 의 을 함수로 변환하는 것은 평가가 이미 수행되었으므로 사용되지 않습니다.
Marc van Leeuwen

13

회고 적 합리화로 인해 주로

  • (새로운 구문을 도입하지 않고) 단락을 보장하려면 연산자를 다음과 같이 제한해야합니다. 결과실제 첫 번째 인수 컨버터블로 bool하고,

  • 단락은 필요할 때 다른 방식으로 쉽게 표현할 수 있습니다.


예를 들어, 클래스가있는 경우 T관련있다 &&||사업자, 다음 식

auto x = a && b || c;

여기서 a, bc유형의 표현되고 T, 단락 등으로 표현 될 수있다

auto&& and_arg = a;
auto&& and_result = (and_arg? and_arg && b : and_arg);
auto x = (and_result? and_result : and_result || c);

또는 아마도 더 명확하게

auto x = [&]() -> T_op_result
{
    auto&& and_arg = a;
    auto&& and_result = (and_arg? and_arg && b : and_arg);
    if( and_result ) { return and_result; } else { return and_result || b; }
}();

명백한 중복성은 운영자 호출로 인한 부작용을 유지합니다.


람다 재 작성이 더 장황한 반면, 캡슐화가 향상되면 그러한 연산자 를 정의 할 수 있습니다 .

나는 다음의 모든 표준 준수를 확신하지는 못하지만 (여전히 약간의 인플루엔자) Visual C ++ 12.0 (2013) 및 MinGW g ++ 4.8.2로 깨끗하게 컴파일됩니다.

#include <iostream>
using namespace std;

void say( char const* s ) { cout << s; }

struct S
{
    using Op_result = S;

    bool value;
    auto is_true() const -> bool { say( "!! " ); return value; }

    friend
    auto operator&&( S const a, S const b )
        -> S
    { say( "&& " ); return a.value? b : a; }

    friend
    auto operator||( S const a, S const b )
        -> S
    { say( "|| " ); return a.value? a : b; }

    friend
    auto operator<<( ostream& stream, S const o )
        -> ostream&
    { return stream << o.value; }

};

template< class T >
auto is_true( T const& x ) -> bool { return !!x; }

template<>
auto is_true( S const& x ) -> bool { return x.is_true(); }

#define SHORTED_AND( a, b ) \
[&]() \
{ \
    auto&& and_arg = (a); \
    return (is_true( and_arg )? and_arg && (b) : and_arg); \
}()

#define SHORTED_OR( a, b ) \
[&]() \
{ \
    auto&& or_arg = (a); \
    return (is_true( or_arg )? or_arg : or_arg || (b)); \
}()

auto main()
    -> int
{
    cout << boolalpha;
    for( int a = 0; a <= 1; ++a )
    {
        for( int b = 0; b <= 1; ++b )
        {
            for( int c = 0; c <= 1; ++c )
            {
                S oa{!!a}, ob{!!b}, oc{!!c};
                cout << a << b << c << " -> ";
                auto x = SHORTED_OR( SHORTED_AND( oa, ob ), oc );
                cout << x << endl;
            }
        }
    }
}

산출:

000-> !! !! || 그릇된
001-> !! !! || 진실
010 화 !! || 그릇된
011-> !! !! || 진실
100-> !! && !! || 그릇된
101-> !! && !! || 진실
110-> !! && !! 진실
111-> !! && !! 진실

여기서 각 !!bang-bang은 로의 변환 bool, 즉 인수 값 확인 을 보여줍니다 .

컴파일러가 쉽게 동일한 작업을 수행하고 추가로 최적화 할 수 있기 때문에 이는 가능한 구현 방법이며 불가능성에 대한 주장은 일반적으로 불가능 성 주장과 동일한 범주, 즉 일반적으로 볼록으로 표시해야합니다.


나는 당신의 단락 대체물, 특히 삼항 대체물을 좋아합니다.
iFreilicht

단락이 없습니다 -다음과 &&같은 추가 줄이 있어야합니다 if (!a) { return some_false_ish_T(); }-첫 번째 글 머리 기호 : 단락은 결과가 아닌 부울로 변환 가능한 매개 변수에 관한 것입니다.
Arne Mertz

@ArneMertz : "Missing"에 대한 귀하의 의견은 의미가 없습니다. 무슨 일인지에 대한 의견, 예, 알고 있습니다. 로 변환 bool할 필요가 단락을.
건배와 hth. -Alf

@ Cheersandhth.- 실종에 대한 의견은 단락의 첫 번째 개정에 대한 ||것이었지만 단락은 하지 않았습니다 &&. 다른 의견은 첫 번째 글 머리표에서 "bool로 변환 가능한 결과 로 제한되어야 함"을 목표로했습니다. " bool 로 변환 가능한 매개 변수 로 제한됨 "imo를 읽어야 합니다.
Arne Mertz

@ArneMertz : 알았어요. 버전을 다시 정했습니다. 죄송합니다. 편집 속도가 느립니다. 제한됨, 아니오 bool표현식에서 추가 연산자의 짧은 순환을 점검하기 위해 변환되어야하기 때문에 제한되어야하는 연산자 결과가 아닙니다. 마찬가지로의 결과 a && b는로 bool논리 OR의 짧은 순환을 확인하기 위해로 변환되어야 합니다 a && b || c.
건배와 hth. -Alf

5

tl; dr : 높은 비용 (특별 구문 필요)과 비교할 때 수요가 매우 적기 때문에 (누가이 기능을 사용할 것인가?) 노력할 가치가 없습니다.

마음에 오는 첫번째 것은 연산자 오버로딩은 사업자의 부울 버전 반면, 쓰기 기능에 단지 멋진 방법이다 ||&&물건 buitlin 있습니다. 컴파일러는 이들 단락의 자유를 가지며, 식 동안 것을 의미 x = y && znonboolean 함께 yz같은 함수의 호출을 야기한다 X operator&& (Y, Z). 즉 , 함수를 호출하기 전에 매개 변수를 모두 평가해야하는 이상한 이름의 함수를 호출 y && z하는 멋진 방법 operator&&(y,z)일뿐입니다 (단락이 적절하다고 간주되는 항목 포함).

그러나, &&연산자 new호출과 operator new그 뒤에 생성자 호출 로 변환되는 연산자 와 같이 연산자 의 변환을 좀 더 정교 하게 만들 수 있어야한다고 주장 할 수 있습니다 .

기술적으로 이것은 문제가되지 않습니다. 단락을 가능하게하는 전제 조건에 특정한 언어 구문을 정의해야합니다. 그러나 단락의 사용 Y은 적절한 경우 X또는 실제로 단락을 수행하는 방법에 대한 추가 정보 가 있어야 하는 경우로 제한됩니다 (즉, 첫 번째 매개 변수의 결과 만 계산). 결과는 다음과 같아야합니다.

X operator&&(Y const& y, Z const& z)
{
  if (shortcircuitCondition(y))
    return shortcircuitEvaluation(y);

  <"Syntax for an evaluation-Point for z here">

  return actualImplementation(y,z);
}

하나는 드물게 과부하 싶어 operator||하고 operator&&드물게 쓰는 경우가 있기 때문에, a && b실제로 것은 nonboolean 맥락에서 직관적이다. 내가 아는 유일한 예외는 표현 템플릿 (예 : 임베디드 DSL)입니다. 그리고 소수의 사례 중 소수만이 단락 평가의 이점을 누릴 수 있습니다. 식 템플릿은 나중에 평가되는 식 트리를 형성하는 데 사용되므로 일반적으로 식 템플릿을 사용하지 않으므로 항상 식의 양쪽이 필요합니다.

한마디로 : 컴파일러 작가도 표준의 저자도는 만에 하나 정의 된 사용자에 단락이 좋을 것이라고 생각 얻을 수 있습니다, 때문에 농구를 통해 뛰어 정의하고 추가 성가신 구문을 구현해야 할 필요성을 느꼈다 operator&&operator||- 단지 손에 논리를 쓰는 것보다 적은 노력이 아니라는 결론에 도달합니다.


비용이 정말 높습니까? D 프로그래밍 언어를 사용하면 lazy인수로 제공된 표현식을 내재적으로 익명 함수로 변환하는 매개 변수를 선언 할 수 있습니다. 이것은 호출 된 함수에 해당 인수를 호출 할 것인지의 여부를 선택합니다. 따라서 언어에 이미 람다가 있으면 필요한 추가 구문이 매우 작습니다. "의사 코드": X 및 (A a, 게으른 B b) {if (cond (a)) {return short (a); } else {actual (a, b ()); }}
BlackJack

@BlackJack 게으른 매개 변수는을 수락하여 구현할 수 있으며 std::function<B()>특정 오버 헤드가 발생합니다. 또는 인라인 할 의향이 있다면 그것을 만드십시오 template <class F> X and(A a, F&& f){ ... actual(a,F()) ...}. 그리고 "normal" B매개 변수로 오버로드 하여 호출자가 선택할 버전을 결정할 수 있습니다. lazy구문은 더 편리 할 수 있지만 특정 성능 트레이드 오프를 가지고있다.
Arne Mertz

1
std::functionvs 의 문제점 중 하나는 lazy첫 번째를 여러 번 평가할 수 있다는 것입니다. 그대로 foo사용되는 게으른 매개 변수 foo+foo는 여전히 한 번만 평가됩니다.
MSalters

"단락의 사용은 Y가 X에 적합하지 않은 경우로 제한 될 것입니다."아니요, 혼자서 X계산할 수있는 경우로 제한됩니다 Y. 매우 다릅니다. std::ostream& operator||(char* a, lazy char*b) {if (a) return std::cout<<a;return std::cout<<b;}. "전환"을 매우 일상적으로 사용하지 않는 한.
Mooing Duck

1
@Sumant 그들은 할 수 있습니다. 그러나 단락 사용자 정의의 논리를 직접 작성할 수도 있습니다 operator&&. 가능하다면 문제는 아니지만 편리한 방법이 짧은 이유는 무엇입니까?
Arne Mertz

5

람다는 게으름을 일으키는 유일한 방법은 아닙니다. 게으른 평가는 C ++의 Expression Templates 를 사용하여 비교적 간단 합니다. 키워드가 필요 없으며 lazyC ++ 98에서 구현할 수 있습니다. 식 트리는 이미 위에서 언급했습니다. 식 템플릿은 가난하지만 영리한 사람의 식 나무입니다. 트릭은 표현식을 재귀 적으로 중첩 된 Expr템플릿 인스턴스화 트리로 변환하는 것 입니다. 나무는 시공 후 별도로 평가됩니다.

다음 코드 는 무료 기능을 제공 하고 로 변환 할 수있는 한 단락 &&||연산자 클래스 S를 구현 합니다 . 코드는 C ++ 14에 있지만 아이디어는 C ++ 98에도 적용됩니다. 라이브 예를 참조하십시오 .logical_andlogical_orbool

#include <iostream>

struct S
{
  bool val;

  explicit S(int i) : val(i) {}  
  explicit S(bool b) : val(b) {}

  template <class Expr>
  S (const Expr & expr)
   : val(evaluate(expr).val)
  { }

  template <class Expr>
  S & operator = (const Expr & expr)
  {
    val = evaluate(expr).val;
    return *this;
  }

  explicit operator bool () const 
  {
    return val;
  }
};

S logical_and (const S & lhs, const S & rhs)
{
    std::cout << "&& ";
    return S{lhs.val && rhs.val};
}

S logical_or (const S & lhs, const S & rhs)
{
    std::cout << "|| ";
    return S{lhs.val || rhs.val};
}


const S & evaluate(const S &s) 
{
  return s;
}

template <class Expr>
S evaluate(const Expr & expr) 
{
  return expr.eval();
}

struct And 
{
  template <class LExpr, class RExpr>
  S operator ()(const LExpr & l, const RExpr & r) const
  {
    const S & temp = evaluate(l);
    return temp? logical_and(temp, evaluate(r)) : temp;
  }
};

struct Or 
{
  template <class LExpr, class RExpr>
  S operator ()(const LExpr & l, const RExpr & r) const
  {
    const S & temp = evaluate(l);
    return temp? temp : logical_or(temp, evaluate(r));
  }
};


template <class Op, class LExpr, class RExpr>
struct Expr
{
  Op op;
  const LExpr &lhs;
  const RExpr &rhs;

  Expr(const LExpr& l, const RExpr & r)
   : lhs(l),
     rhs(r)
  {}

  S eval() const 
  {
    return op(lhs, rhs);
  }
};

template <class LExpr>
auto operator && (const LExpr & lhs, const S & rhs)
{
  return Expr<And, LExpr, S> (lhs, rhs);
}

template <class LExpr, class Op, class L, class R>
auto operator && (const LExpr & lhs, const Expr<Op,L,R> & rhs)
{
  return Expr<And, LExpr, Expr<Op,L,R>> (lhs, rhs);
}

template <class LExpr>
auto operator || (const LExpr & lhs, const S & rhs)
{
  return Expr<Or, LExpr, S> (lhs, rhs);
}

template <class LExpr, class Op, class L, class R>
auto operator || (const LExpr & lhs, const Expr<Op,L,R> & rhs)
{
  return Expr<Or, LExpr, Expr<Op,L,R>> (lhs, rhs);
}

std::ostream & operator << (std::ostream & o, const S & s)
{
  o << s.val;
  return o;
}

S and_result(S s1, S s2, S s3)
{
  return s1 && s2 && s3;
}

S or_result(S s1, S s2, S s3)
{
  return s1 || s2 || s3;
}

int main(void) 
{
  for(int i=0; i<= 1; ++i)
    for(int j=0; j<= 1; ++j)
      for(int k=0; k<= 1; ++k)
        std::cout << and_result(S{i}, S{j}, S{k}) << std::endl;

  for(int i=0; i<= 1; ++i)
    for(int j=0; j<= 1; ++j)
      for(int k=0; k<= 1; ++k)
        std::cout << or_result(S{i}, S{j}, S{k}) << std::endl;

  return 0;
}

5

논리 연산자는 연관된 진리표의 평가에서 "최적화"이기 때문에 단락이 허용됩니다. 논리 자체 의 기능 이며이 논리가 정의됩니다.

과부하 이유 실제로 거기 &&||쇼트을은?

사용자 정의 오버로드 된 논리 연산자는 이러한 진리표의 논리를 따를 의무없습니다 .

그러나 오버로드 될 때 왜이 동작이 손실됩니까?

따라서 전체 기능을 정상대로 평가해야합니다. 컴파일러는이를 일반 과부하 연산자 (또는 함수)로 취급해야하며 다른 함수와 마찬가지로 최적화를 적용 할 수 있습니다.

사람들은 다양한 이유로 논리 연산자를 과부하시킵니다. 예를 들어; 그것들은 사람들이 익숙한 "정상적인"논리적 인 것이 아닌 특정 영역에서 특정한 의미를 가질 수 있습니다.


4

단락은 "및"및 "또는"의 진리표 때문입니다. 사용자가 어떤 작업을 정의 할 것인지 어떻게 알 수 있으며 두 번째 연산자를 평가할 필요가 없다는 것을 어떻게 알 수 있습니까?


의견과 @ 중복 제거기 답변에서 언급했듯이 추가 언어 기능으로 가능할 수 있습니다. 지금은 작동하지 않는다는 것을 알고 있습니다. 내 질문은 그런 기능이 없다는 이유가 무엇인지에 대한 질문이었습니다.
iFreilicht

사용자 정의에 대한 추측을해야한다는 점을 고려하면 확실히 복잡한 기능 일 것입니다!
nj-ath

무엇에 대한 : (<condition>)두 번째 인수가 평가되지되는 조건을 지정하는 연산자 선언 후?
iFreilicht

@iFreilicht : 여전히 다른 단항 함수 본문이 필요합니다.
MSalters

3

그러나 bool의 연산자에는 이러한 동작이 있습니다. 왜이 단일 유형으로 제한되어야합니까?

이 부분에 답하고 싶습니다. 그 이유는 내장 연산자 &&||표현식이 오버로드 연산자처럼 함수로 구현되지 않았기 때문입니다.

특정 식에 대한 컴파일러의 이해에 내장 된 단락 논리를 갖는 것은 쉽습니다. 다른 내장 제어 흐름과 같습니다.

그러나 연산자 오버로드는 대신 특정 함수를 갖는 함수로 구현됩니다. 그 중 하나는 함수로 호출되기 전에 인수로 사용 된 모든 표현식을 평가한다는 것입니다. 분명히 다른 규칙을 정의 할 수는 있지만 더 큰 일입니다.


1
대가가의 질문에 주어졌다 궁금 여부 의 과부하 &&, ||,허용해야 하는가? C ++에 오버로드가 함수 호출 이외의 다른 방식으로 작동 할 수있는 메커니즘이 없다는 사실은 해당 함수의 오버로드가 다른 작업을 수행 할 수없는 이유를 설명하지만 이러한 연산자가 처음에 오버로드 가능한 이유는 설명하지 않습니다. 실제 이유는 단순히 많은 생각없이 연산자 목록에 던져 졌기 때문이라고 생각합니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.