수색…


enable_if

std::enable_if 는 부울 조건을 사용하여 SFINAE를 트리거하는 편리한 유틸리티입니다. 그것은 다음과 같이 정의됩니다 :

template <bool Cond, typename Result=void>
struct enable_if { };

template <typename Result>
struct enable_if<true, Result> {
    using type = Result;
};

즉, enable_if<true, R>::typeR 의 별칭입니다. enable_if<false, T>::typeenable_if 특수화에 type 멤버 유형이 없으므로 enable_if<false, T>::type 이 잘못되었습니다.

std::enable_if 는 템플릿을 제한하는 데 사용할 수 있습니다.

int negate(int i) { return -i; }

template <class F>
auto negate(F f) { return -f(); }

여기서, negate(1) 대한 호출은 모호함으로 인해 실패합니다. 하지만 두 번째 오버로드는 정수 유형에 사용되지 않으므로 다음을 추가 할 수 있습니다.

int negate(int i) { return -i; }

template <class F, class = typename std::enable_if<!std::is_arithmetic<F>::value>::type>
auto negate(F f) { return -f(); }

이제! negate<int> 인스턴스화하면 !std::is_arithmetic<int>::valuefalse 이므로 대체 오류가 발생합니다. SFINAE로 인해 이것은 심각한 오류는 아니지만이 후보는 단순히 오버로드 집합에서 제거됩니다. 결과적으로 negate(1) 에는 실행 가능한 후보자가 하나뿐입니다. 그러면이 후보가 호출됩니다.

언제 사용 하는가?

std::enable_ifstd::enable_if 위에 헬퍼 되지만 SFINAE가 처음부터 작동하게하는 것은 아닙니다. std::size 와 비슷한 기능을 구현하는 두 가지 대안, 즉 컨테이너 또는 배열의 크기를 생성하는 오버로드 세트 size(arg) 를 고려해 보겠습니다.

// for containers
template<typename Cont>
auto size1(Cont const& cont) -> decltype( cont.size() );

// for arrays
template<typename Elt, std::size_t Size>
std::size_t size1(Elt const(&arr)[Size]);

// implementation omitted
template<typename Cont>
struct is_sizeable;

// for containers
template<typename Cont, std::enable_if_t<std::is_sizeable<Cont>::value, int> = 0>
auto size2(Cont const& cont);

// for arrays
template<typename Elt, std::size_t Size>
std::size_t size2(Elt const(&arr)[Size]);

is_sizeable 이 적절히 작성되었다고 가정하면이 두 선언은 SFINAE와 정확히 동일해야합니다. 어느 것이 가장 쓰기 쉽고 한 번에 리뷰하고 이해하는 것이 가장 쉽습니다.

이제 랩 어라운드 또는 모듈 식 동작을 위해 부호있는 정수 오버플로를 피하는 산술 헬퍼를 구현하는 방법을 고려해 보겠습니다. incr(i, 3) 예를 들어 incr(i, 3)i += 3 과 같을 것입니다. 사실 iINT_MAX 값을 가진 int 경우에도 결과가 항상 정의된다는 사실을 알 수 있습니다. 다음 두 가지 가능한 대안이 있습니다.

// handle signed types
template<typename Int>
auto incr1(Int& target, Int amount)
-> std::void_t<int[static_cast<Int>(-1) < static_cast<Int>(0)]>;

// handle unsigned types by just doing target += amount
// since unsigned arithmetic already behaves as intended
template<typename Int>
auto incr1(Int& target, Int amount)
-> std::void_t<int[static_cast<Int>(0) < static_cast<Int>(-1)]>;
 
template<typename Int, std::enable_if_t<std::is_signed<Int>::value, int> = 0>
void incr2(Int& target, Int amount);
 
template<typename Int, std::enable_if_t<std::is_unsigned<Int>::value, int> = 0>
void incr2(Int& target, Int amount);

다시 한번 말하면 가장 쓰기 쉽고 한 눈에 검토하고 이해하는 것이 가장 쉽습니다.

std::enable_if 의 강점은 리팩토링 및 API 디자인에서 어떻게 사용되는지입니다. is_sizeable<Cont>::valuecont.size() 가 유효한지 여부를 반영하려는 경우 size1 대해 표시되는 표현식을 사용하는 것이 더 간결해질 수 있지만 is_sizeable 이 여러 위치에서 사용되는지 여부에 따라 달라질 수 있습니다. . 그것의 구현이 incr1 의 선언으로 유출 될 때보 다 훨씬 더 명확하게 의도를 반영하는 std::is_signed 와 대조를 incr1 .

void_t

C ++ 11

void_t 는 모든 (유형의) 유형을 void 유형으로 맵핑하는 메타 기능입니다. void_t 의 주된 목적은 타입 특성의 작성을 용이하게하는 것이다.

std::void_t 는 C ++ 17의 일부가 될 것이다. 그러나 그때까지 구현하는 것은 매우 간단하다.

template <class...> using void_t = void;

일부 컴파일러 에는 약간 다른 구현이 필요 합니다.

template <class...>
struct make_void { using type = void; };

template <typename... T>
using void_t = typename make_void<T...>::type;

void_t 의 주요 응용 프로그램은 void_t 형식의 유효성을 검사하는 형식 특성을 작성하는 것입니다. 예를 들어, 타입에 인수가없는 foo() 멤버 함수가 있는지 확인해 봅시다.

template <class T, class=void>
struct has_foo : std::false_type {};

template <class T>
struct has_foo<T, void_t<decltype(std::declval<T&>().foo())>> : std::true_type {};

이게 어떻게 작동합니까? has_foo<T>::value 를 인스턴스화하려고하면 컴파일러가 has_foo<T, void> 대한 최상의 전문화를 has_foo<T, void> 합니다. 기본 식과 기본 식을 인스턴스화해야하는 보조 식의 두 가지 옵션이 있습니다.

  • T foo() 멤버 함수를 가지고 있다면 리턴되는 타입은 void 로 변환되고, 템플릿 부분 순서 지정 규칙에 따라 specialization이 primary에 우선합니다. 따라서 has_foo<T>::valuetrue
  • 경우 T 이러한 멤버 함수가없는 (또는 하나 개 이상의 인수가 필요합니다), 다음 대체 전문화 실패하고 우리는 대신에 사용하는 기본 템플릿을 가지고있다. 따라서 has_foo<T>::valuefalse 입니다.

간단한 경우 :

template<class T, class=void>
struct can_reference : std::false_type {};

template<class T>
struct can_reference<T, std::void_t<T&>> : std::true_type {};

이것은 std::declval 또는 decltype 사용하지 않습니다.

void 인수의 공통 패턴을 볼 수 있습니다. 우리는 이것을 배제 할 수 있습니다.

struct details {
  template<template<class...>class Z, class=void, class...Ts>
  struct can_apply:
    std::false_type
  {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
    std::true_type
  {};
};

template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

std::void_t 의 사용을 숨기고 can_apply 가 첫 번째 템플릿 인수로 제공된 유형이 다른 유형으로 대체 된 후에 올바른 형식인지 여부를 표시기처럼 작동하게합니다. 이전 예제는 이제 can_apply 를 사용하여 can_apply 과 같이 재 작성 될 수 있습니다.

template<class T>
using ref_t = T&;

template<class T>
using can_reference = can_apply<ref_t, T>;    // Is T& well formed for T?

과:

template<class T>
using dot_foo_r = decltype(std::declval<T&>().foo());

template<class T>
using can_dot_foo = can_apply< dot_foo_r, T >;    // Is T.foo() well formed for T?

원래 버전보다 단순 해 보입니다.

can_apply 와 비슷한 std 형질에 대한 post-C ++ 17 제안이 있습니다.

void_t 의 유용성은 Walter Brown에 의해 발견되었습니다. 그는 CppCon 2016에서 멋진 프레젠테이션 을했습니다.

함수 템플릿에서 뒤에 오는 decltype

C ++ 11

제약 함수 중 하나는 뒤에 오는 decltype 을 사용하여 반환 유형을 지정하는 것입니다.

namespace details {
   using std::to_string;

   // this one is constrained on being able to call to_string(T)
   template <class T>
   auto convert_to_string(T const& val, int )
       -> decltype(to_string(val))
   {
       return to_string(val);
   }

   // this one is unconstrained, but less preferred due to the ellipsis argument
   template <class T>
   std::string convert_to_string(T const& val, ... )
   {
       std::ostringstream oss;
       oss << val;
       return oss.str();
   }
}

template <class T>
std::string convert_to_string(T const& val)
{
    return details::convert_to_string(val, 0);
}

to_string() 호출 할 수있는 인수로 convert_to_string() 을 호출 to_string() details::convert_to_string() 은 두 가지 실행 가능한 함수가 있습니다. details::convert_to_string() . 0 에서 int 로의 변환이 0 에서 ... 로의 변환보다 더 나은 암시 적 변환 시퀀스이므로 첫 번째가 선호됩니다 ...

to_string() 호출 할 수없는 인수로 convert_to_string() 을 호출하면 첫 번째 함수 템플리트 인스턴스가 대체 실패로 연결됩니다 ( decltype(to_string(val)) ). 결과적으로 해당 후보가 오버로드 집합에서 제거됩니다. 두 번째 함수 템플릿은 제약이 없으므로 선택되어 대신 operator<<(std::ostream&, T) . 그 중 하나가 정의되지 않은 경우, 우리는 라인 oss << val 에 템플릿 스택에 하드 컴파일 오류가 있습니다.

SFINAE 란 무엇입니까?

SFINAE 내가 n은 n 개의 E의 rror 오티 s의 S의 ubstitution F의 ailure을 의미합니다. 함수 템플릿 또는 클래스 템플릿을 인스턴스화하기 위해 형식 (또는 값)을 대체하여 생성 된 잘못된 코드는 하드 컴파일 오류가 아니며 공제 실패로만 처리됩니다.

함수 템플릿 인스턴스화 또는 클래스 템플릿 전문화에 대한 공제 실패는 고려 대상에서 해당 후보를 제거합니다. 실패한 응시자는 처음부터 존재하지 않는 것처럼 말입니다.

template <class T>
auto begin(T& c) -> decltype(c.begin()) { return c.begin(); }

template <class T, size_t N>
T* begin(T (&arr)[N]) { return arr; }

int vals[10];
begin(vals); // OK. The first function template substitution fails because
             // vals.begin() is ill-formed. This is not an error! That function
             // is just removed from consideration as a viable overload candidate,
             // leaving us with the array overload. 

즉각적인 컨텍스트 에서의 대체 오류 만이 공제 실패로 간주되며 다른 모든 오류는 하드 오류로 간주됩니다.

template <class T>
void add_one(T& val) { val += 1; }

int i = 4;
add_one(i); // ok

std::string msg = "Hello";
add_one(msg); // error. msg += 1 is ill-formed for std::string, but this
              // failure is NOT in the immediate context of substituting T

enable_if_all / enable_if_any

C ++ 11

동기 부여의 예


다음 코드 스 니펫과 같이 템플릿 매개 변수 목록에 가변적 인 템플릿 팩이있는 경우

template<typename ...Args> void func(Args &&...args) { //... };

(이전의 C ++ 17) 표준 라이브러리의 모든 매개 변수에 SFINAE 제약을 부과하는 enable_if 쓸 수있는 직접적인 방법 제공하지 않습니다 Args 또는 매개 변수의 Args . C ++ 17 제공 std::conjunctionstd::disjunction 이 문제를 해결한다. 예 :

/// C++17: SFINAE constraints on all of the parameters in Args.
template<typename ...Args,
         std::enable_if_t<std::conjunction_v<custom_conditions_v<Args>...>>* = nullptr>
void func(Args &&...args) { //... };

/// C++17: SFINAE constraints on any of the parameters in Args.
template<typename ...Args,
         std::enable_if_t<std::disjunction_v<custom_conditions_v<Args>...>>* = nullptr>
void func(Args &&...args) { //... };

C ++ 17을 사용할 수 없다면이를 달성 할 수있는 몇 가지 솔루션이 있습니다. 그 중 하나는이 질문의 답안에서 보여 지듯이 기본 케이스 클래스와 부분 전문화 를 사용하는 것입니다.

또는 std::disjunctionstd::disjunction 의 동작을 std::conjunction 직접 구현할 수도 있습니다. 다음 예제에서는 구현을 설명하고 std::enable_if 와 결합하여 두 개의 별칭 ( enable_if_allenable_if_any 을 생성합니다.이 별칭은 의미 론적으로 정확히 수행해야합니다. 이는보다 확장 가능한 솔루션을 제공 할 수 있습니다.


enable_if_all enable_if_any 구현


최초의 에뮬레이트 할 수 std::conjunctionstd::disjunction 사용자 정의하여 seq_andseq_or 각각 :

/// Helper for prior to C++14.
template<bool B, class T, class F >
using conditional_t = typename std::conditional<B,T,F>::type;

/// Emulate C++17 std::conjunction.
template<bool...> struct seq_or: std::false_type {};
template<bool...> struct seq_and: std::true_type {};

template<bool B1, bool... Bs>
struct seq_or<B1,Bs...>: 
  conditional_t<B1,std::true_type,seq_or<Bs...>> {};

template<bool B1, bool... Bs>
struct seq_and<B1,Bs...>:
  conditional_t<B1,seq_and<Bs...>,std::false_type> {};  

그렇다면 구현은 간단합니다.

template<bool... Bs>
using enable_if_any = std::enable_if<seq_or<Bs...>::value>;

template<bool... Bs>
using enable_if_all = std::enable_if<seq_and<Bs...>::value>;

결국 일부 헬퍼 :

template<bool... Bs>
using enable_if_any_t = typename enable_if_any<Bs...>::type;

template<bool... Bs>
using enable_if_all_t = typename enable_if_all<Bs...>::type;

용법


사용법 또한 간단합니다.

    /// SFINAE constraints on all of the parameters in Args.
    template<typename ...Args,
             enable_if_all_t<custom_conditions_v<Args>...>* = nullptr>
    void func(Args &&...args) { //... };

    /// SFINAE constraints on any of the parameters in Args.
    template<typename ...Args,
             enable_if_any_t<custom_conditions_v<Args>...>* = nullptr>
    void func(Args &&...args) { //... };

is_detected

type_trait 생성을 일반화하기 위해 : SFINAE를 기반으로 실험적 특성 인 detected_or , detected_t , is_detected 있습니다.

템플릿 매개 변수 사용 typename Default , template <typename...> Optypename ... Args :

  • is_detected : Op<Args...> 의 유효성에 따라 std::true_type 또는 std::false_type 별칭
  • detected_t : 별칭 Op<Args...> 또는 nonesuch 의 유효 따라 Op<Args...> .
  • detected_or : value_t 가있는 구조체의 별칭은 is_detected 이며 Op<Args...> 또는 Op<Args...> 의 유효성에 따라 Defaulttype 입니다 Op<Args...>

다음과 같이 std::void_t 를 사용하여 구현할 수 있습니다.

C ++ 17
namespace detail {
    template <class Default, class AlwaysVoid,
              template<class...> class Op, class... Args>
    struct detector
    {
        using value_t = std::false_type;
        using type = Default;
    };

    template <class Default, template<class...> class Op, class... Args>
    struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
    {
        using value_t = std::true_type;
        using type = Op<Args...>;
    };

} // namespace detail

// special type to indicate detection failure
struct nonesuch {
    nonesuch() = delete;
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    void operator=(nonesuch const&) = delete;
};

template <template<class...> class Op, class... Args>
using is_detected =
    typename detail::detector<nonesuch, void, Op, Args...>::value_t;

template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;

template <class Default, template<class...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;

방법의 존재를 감지하는 특성은 간단히 구현 될 수 있습니다.

typename <typename T, typename ...Ts>
using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));

struct C1 {};

struct C2 {
    int foo(char) const;
};

template <typename T>
using has_foo_char = is_detected<foo_type, T, char>;

static_assert(!has_foo_char<C1>::value, "Unexpected");
static_assert(has_foo_char<C2>::value, "Unexpected");

static_assert(std::is_same<int, detected_t<foo_type, C2, char>>::value,
              "Unexpected");

static_assert(std::is_same<void, // Default
                           detected_or<void, foo_type, C1, char>>::value,
              "Unexpected");
static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value,
              "Unexpected");

다양한 옵션으로 과부하 해결

여러 옵션 중에서 선택해야하는 경우 enable_if<> 를 통해 enable_if<> 활성화하면 여러 조건이 무효화되어야하기 때문에 상당히 복잡 할 수 있습니다.

오버로드 사이의 순서는 대신 상속, 즉 태그 디스패치를 ​​사용하여 선택할 수 있습니다.

올바른 형식이 필요한 것을 테스트하고 다른 모든 버전 조건의 부정을 테스트하는 대신, 대신 우리가 필요로하는 것을 테스트합니다. 바람직하게는 후행 반환에서 decltype 을 테스트합니다.
이로 인해 여러 옵션이 잘 형성 될 수 있으며 iterator-trait 태그 ( random_access_tag 외)와 유사한 '태그'를 사용하는 태그를 random_access_tag 있습니다. 기본 클래스의 기본 클래스보다 나은 기본 클래스보다 직접 일치가 더 좋기 때문에이 방법이 효과적입니다.

#include <algorithm>
#include <iterator>

namespace detail
{
    // this gives us infinite types, that inherit from each other
    template<std::size_t N>
    struct pick : pick<N-1> {};
    template<>
    struct pick<0> {};

    // the overload we want to be preferred have a higher N in pick<N>
    // this is the first helper template function
    template<typename T>
    auto stable_sort(T& t, pick<2>)
        -> decltype( t.stable_sort(), void() )
    {
        // if the container have a member stable_sort, use that
        t.stable_sort();
    }

    // this helper will be second best match
    template<typename T>
    auto stable_sort(T& t, pick<1>)
        -> decltype( t.sort(), void() )
    {
        // if the container have a member sort, but no member stable_sort
        // it's customary that the sort member is stable
        t.sort();
    }

    // this helper will be picked last
    template<typename T>
    auto stable_sort(T& t, pick<0>)
        -> decltype( std::stable_sort(std::begin(t), std::end(t)), void() )
    {
        // the container have neither a member sort, nor member stable_sort
        std::stable_sort(std::begin(t), std::end(t));
    }

}

// this is the function the user calls. it will dispatch the call
// to the correct implementation with the help of 'tags'.
template<typename T>
void stable_sort(T& t)
{
    // use an N that is higher that any used above.
    // this will pick the highest overload that is well formed.
    detail::stable_sort(t, detail::pick<10>{});
}

완전 일치가 변환보다 낫고 생략 부호보다 낫다는 것과 같이 오버로드를 구분하는 데 일반적으로 사용되는 다른 방법이 있습니다.

그러나, 태그 - 디스패치는 임의의 수의 선택으로 확장 될 수 있으며, 의도에서 좀 더 명확하다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow