More C++ Idioms (85. Tag Dispatching)

2024. 8. 4. 22:05프로그래밍 (확장)/More C++ Idioms

C++에서 함수 오버로딩과 템플릿을 활용하여 조건에 따라 다른 함수 호출을 선택하는 기법으로 조건에 따른 코드를 간결하고 효율적으로 작성할 수 있다.

 

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Tag_Dispatching

 

More C++ Idioms/Tag Dispatching - Wikibooks, open books for an open world

From Wikibooks, open books for an open world Jump to navigation Jump to search Simplify writing multiple SFINAE-constrained overloads. Tag dispatching is a useful complement to enable_if. It can also be used in conjunction with trailing return type and dec

en.wikibooks.org

 

More C++ Idioms의 예시를 보면

namespace detail
{
    // empty class (https://gangdonggil.tistory.com/65)
    struct pick_3{};
    struct pick_2 : public pick_3{};
    struct pick_1 : public pick_2{};
    constexpr pick_1 selector{};
}

 

우선순위가 pick_1 > pick_2 > pick_3이 되도록 상속구조가 잡혀 있다.

selector는 기본적으로 pick_1을 사용하도록 되어 있으므로 pick_1을 먼저 확인 할 것이다.

 

각 함수 템플릿의 구현부를 보면

auto function() -> decltype() 에서 함수 템플릿의 반환 타입을 조건부로 결정하게 되는데, 이 조건이 만족되어야 함수가 정의된다. (이 부분은 별도로 다루게 되면 링크 추가 예정)

 

우선순위가 가장 높은 pick_1을 인자로 구현된 첫번째 remove_if를 살펴보면,

template <typename Cont, typename Op>
auto remove_if(pick_1, Cont& cont, Op&& op)
    -> decltype(cont.remove_if(std::forward<Op>(op)), void())
{
    cont.remove_if(std::forward<Op>(op));
}

(decltype & SFINAE) 컴파일 타임에 cont.remove_if가 유효한지 검사. 유효하면 이 함수를 우선 호출하게 됨.

 

template <typename Cont, typename Op>
auto remove_if(pick_2, Cont& cont, Op&& op)
    -> decltype(cont.erase(std::remove_if(std::begin(cont), std::end(cont), std::forward<Op>(op)), std::end(cont)), void())
{
    cont.erase(std::remove_if(std::begin(cont), std::end(cont), std::forward<Op>(op)), std::end(cont));
}

(decltype & SFINAE) cont.erase(std::remove_if(std::begin(cont), std::end(cont)가 유효한지 검사. 가능한 경우 이 함수 호출.

 

template <typename Cont, typename Op>
void remove_if(pick_3, Cont& cont, Op&& op)
{
    auto it = std::begin(cont);
    auto end = std::end(cont);
    while (it != end)
    {
        if (std::invoke_r<bool>(std::forward<Op>(op), *it))
            it = cont.erase(it);
        else
            ++it;
    }
}

수동으로 반복해서 조건에 해당하는 요소를 제거하는 함수로 앞의 두 함수가 모두 실패하면 호출된다.

 

 

호출하는 코드의 예시를 만들어 본다면,

std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
remove_if(vec, [](int n) { return n % 2 == 0; }); // 짝수 제거

이 경우 vector는 remove_if 맴버 함수가 없으므로 첫 번째 함수는 제외되고, erase와 std::remove_if를 사용할 수 있으므로 pick_2 함수를 선택해서 사용하게 된다.

 

연관 글:

https://gangdonggil.tistory.com/65

 

std::is_empty (C++11~)

출처: https://en.cppreference.com/w/cpp/types/is_empty struct is_empty; (since C++11) std::is_empty is a UnaryTypeTrait. If T is an empty type (that is, a non-union class type with no non-static data members other than bit-fields of size 0, no virtual f

gangdonggil.tistory.com

 

https://gangdonggil.tistory.com/73

 

auto -> decltype(표현식)

함수 반환 타입을 지정할 때 사용하는 구문으로 함수의 반환 타입을 컴파일러가 자동으로 추론하고, 그 타입을 decltype을 사용하여 명시적으로 지정한다. 특히 함수 템플릿에서 유용하고, 주로

gangdonggil.tistory.com

 

https://gangdonggil.tistory.com/77

 

SFINAE (Substitution Failure Is Not An Error)

출처: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/SFINAE More C++ Idioms/SFINAE - Wikibooks, open books for an open worldFrom Wikibooks, open books for an open world Jump to navigation Jump to search Prune functions that do not yield valid templat

gangdonggil.tistory.com