C ++ 함수 템플릿 부분 전문화?


87

아래 코드가 클래스의 부분적인 전문화라는 것을 알고 있습니다.

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

또한 C ++에서는 함수 템플릿 부분 전문화를 허용하지 않는다는 것을 알고 있습니다 (전체 만 허용됨). 하지만 내 코드는 하나 / 동일한 유형 인수에 대해 함수 템플릿을 부분적으로 전문화했음을 의미합니까? Microsoft Visual Studio 2010 Express에서 작동하기 때문입니다! 아니라면 부분 전문화 개념을 설명해 주시겠습니까?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}

클래스 전문화의 비유를 찾으십시오. 클래스 전문화라고하면 왜 오버로딩과 같은 기능을 고려해야할까요 ??
Narek 2011

1
아니요, 전문화 구문이 다릅니다. 아래 내 대답에서 (추정 된) 함수 전문화 구문을 살펴보십시오.
iammilind 2011

2
"최대 호출이 모호합니다"오류가 발생하지 않는 이유는 무엇입니까? 어떻게 max(5,5)해결 max(T const&, T const&) [with T=int]하지 max(T1 const&, T2 const&) [with T1=int and T2=int]않습니까?
NHDaly

답변:


81

기능 부분 전문화는 아직 표준에 따라 허용되지 않습니다. 이 예에서는 실제로 오버로딩 되고 max<T1,T2>기능을 전문화하지 않습니다 .
구문은 봤어야 어느 정도 는 허용했다, 아래와 같이 :

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

함수 템플릿의 경우 컴파일러 확장을 제외하고 C ++ 표준에서 전체 전문화 만 허용됩니다!


1
@Narek, 부분 함수 전문화는 어떤 이유로 든 표준의 일부가 아닙니다. 나는 MSVC가 그것을 확장으로 지원한다고 생각합니다. 언젠가는 다른 컴파일러에서도 허용됩니다.
iammilind 2011

1
@iammilind : 문제 없습니다. 그는 이미 그것을 알고있는 것 같습니다. 그것이 그가 함수 템플릿에도 그것을 시도하는 이유입니다. 그래서 나는 그것을 다시 편집하여 지금 명확히했습니다.
Nawaz

19
부분 전문화가 허용되지 않는 이유 를 설명 할 수있는 사람이 있습니까?
HelloGoodbye

2
@NHDaly, 한 기능이 다른 기능보다 더 잘 일치하기 때문에 모호성 오류가 발생하지 않습니다. for를 선택 (T, T)하는 이유는 전자가 2 개의 매개 변수가 있고 두 유형이 동일하다는 것을 보장하기 때문입니다. 후자는 2 개의 매개 변수가 있음을 보장합니다. 컴파일러는 항상 정확한 설명을 선택합니다. 예를 들어 "강"에 대한 두 가지 설명 중 하나를 선택해야한다면 어느 것을 선택 하시겠습니까? "물 수집"대 "흐르는 물 수집". (T1, T2)(int, int)
iammilind

1
@kfsone,이 기능은 검토 중이므로 해석이 가능합니다. 당신은 참조 할 수 있습니다 이 오픈 표준 섹션 에서 I 톱, C ++ 표준 함수 템플릿 부분 특수화를 허용하지 않는 이유는 무엇입니까?
iammilind

43

다른 답변에서 지적했듯이 부분 전문화는 허용되지 않으므로 아래와 같이 std::is_same및 을 사용하여 해결할 수 std::enable_if있습니다.

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

산출:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

편집 : 남은 다른 모든 케이스를 처리 할 수 ​​있어야하는 경우 이미 처리 된 케이스가 일치 하지 않아야 함을 나타내는 정의를 추가 할 수 있습니다. 그렇지 않으면 모호한 정의에 빠집니다. 정의는 다음과 같습니다.

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

다음을 생성합니다.

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

모든 경우 가 약간 지루해 보이지만 이미 수행 한 모든 작업을 컴파일러에 알려야하므로 최대 5 개 또는 몇 가지 전문화를 처리하는 것이 가능합니다.


이 작업은 훨씬 간단하고 명확한 방식으로 함수 오버로딩에 의해 처리 될 수 있으므로 실제로 수행 할 필요가 없습니다.
아드리안

2
@Adrian 나는 이것을 해결하기 위해 다른 함수 오버로딩 접근 방식을 정말로 생각할 수 없습니다. 부분적인 과부하가 허용되지 않는다는 것을 알았습니까? 더 명확하다고 생각되면 솔루션을 공유하십시오.
Rubens 2015 년

1
모든 템플릿 함수를 쉽게 잡을 있는 다른 방법이 있습니까?
Nick

15

전문화 란 무엇입니까?

템플릿을 정말로 이해하고 싶다면 함수형 언어를 살펴 봐야합니다. C ++의 템플릿 세계는 순전히 기능적인 하위 언어입니다.

기능적 언어에서는 패턴 일치를 사용하여 선택합니다 .

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

당신이 볼 수 있듯이, 우리는 오버로드 의 정의를isJust .

음, C ++ 클래스 템플릿은 똑같은 방식으로 작동합니다. 매개 변수의 수와 특성을 나타내는 기본 선언을 제공합니다 . 선언 일 수도 있고 정의 역할도 할 수 있으며 (원하는 경우) 패턴의 전문화를 제공하고 클래스의 다른 (그렇지 않으면 어리석은) 버전을 연결할 수 있습니다. .

템플릿 함수의 경우 전문화는 다소 어색합니다. 오버로드 해결과 다소 충돌합니다. 따라서 전문화는 비전 문화 버전과 관련이 있으며 과부하 해결 중에는 전문화가 고려되지 않는 것으로 결정되었습니다. 따라서 올바른 기능을 선택하는 알고리즘은 다음과 같습니다.

  1. 일반 기능 및 비전문 템플릿간에 과부하 해결 수행
  2. 특수화되지 않은 템플릿을 선택한 경우 더 잘 일치하는 전문화가 있는지 확인하십시오.

(심층 치료에 대해서는 GotW # 49 참조 )

따라서 기능의 템플릿 전문화는 문자 그대로 두 번째 영역 시민입니다. 내가 생각하는 한, 우리는 그것들 없이는 더 나을 것입니다. 나는 아직 템플릿 전문화 사용이 오버로딩으로 해결 될 수없는 경우를 만나지 못했습니다.

템플릿 전문화입니까?

아니요, 단순히 과부하이며 괜찮습니다. 실제로 오버로드는 일반적으로 예상대로 작동하지만 전문화는 놀라 울 수 있습니다 (제가 링크 한 GotW 기사를 기억하십시오).


"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."비 유형 템플릿 매개 변수는 어떻습니까?
Jules GM

@Julius :과 같은 더미 매개 변수를 도입하여도 여전히 오버로딩을 사용할 수 있습니다 boost::mpl::integral_c<unsigned, 3u>. 또 다른 해결책은 enable_if/ 를 사용하는 것일 수도 disable_if있지만 이야기는 다릅니다.
Matthieu M.

7

클래스가 아닌 가변 부분 전문화는 허용되지 않지만 다음과 같습니다.

컴퓨터 과학의 모든 문제는 다른 수준의 간접적 인 방법으로 해결 될 수 있습니다. —— 데이비드 휠러

함수 호출을 전달하기 위해 클래스를 추가하면이 문제를 해결할 수 있습니다. 예는 다음과 같습니다.

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

4

예를 들어 합법적으로 전문화 std::swap할 수 있지만 자신의 과부하를 합법적으로 정의 할 수는 없습니다. 즉, std::swap사용자 정의 클래스 템플릿에 대해 작업 할 수 없습니다 .

오버로딩과 부분 전문화는 경우에 따라 동일한 효과를 가질 수 있지만 모두와는 거리가 멀습니다.


4
이것이 swap네임 스페이스에 과부하 를 넣는 이유 입니다.
jpalecek 2011

2

늦은 답변이지만 일부 늦은 독자는 유용하다고 생각할 수 있습니다. 때로는 특수화 될 수 있도록 설계된 도우미 기능도 문제를 해결할 수 있습니다.

그래서 상상해 봅시다. 이것이 우리 가 해결 하려고 한 것입니다.

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

좋아요, 부분 템플릿 함수 전문화, 우리는 그렇게 할 수 없습니다 ... 그래서 전문화에 필요한 부분을 도우미 함수로 "내보내고"그 부분을 전문화하고 사용합시다 :

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

이것은 특히 대안 (전문화 대신 정상적인 오버로드, Rubens가 제안한 해결 방법 ... – 이것이 나쁘거나 내 것이 더 낫다는 것이 아니라 다른 것)가 상당히 많은 공통 코드를 공유 경우 흥미로울 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.