簡體   English   中英

一種安全,符合標准的方法,只有在實例化了static_assert的情況下,才能使類模板專業化而無法編譯?

[英]A safe, standard-compliant way to make a class template specialization fail to compile using `static_assert` only if it is instantiated?

假設我們要創建一個只能用數字實例化的模板類,否則就不應該編譯。 我的嘗試:

#include <type_traits>

template<typename T, typename = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(std::is_same<C,T>::value, "T is not arithmetic type.");

    //OnlyNumbers<C>* ptr;
};

template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>{};

struct Foo{};
int main()
{
    OnlyNumbers<int>{}; //Compiles
    //OnlyNumbers<Foo>{}; //Error
}

現場演示 -所有三個主要的編譯器似乎都能按預期工作。 我知道已經有一個類似的問題 ,答案引用了該標准。 接受的答案使用temp.res.8temp.dep.1來回答該問題。 我認為我的問題有所不同,因為我只是在問我的示例,我不確定標准對此的看法。

我認為我的程序不是格式錯誤的,並且僅當編譯器嘗試實例化基本模板時,它才應該無法編譯。 我的推理:

  • [temp.dep.1]:

    在模板內部,某些構造的語義可能因一個實例而異。 這樣的構造取決於模板參數。

    這應該使std::is_same<C,T>::value取決於T

  • [temp.res.8.1]:

    如果模板中的語句或模板未實例化,則無法為模板或constexpr的子語句生成有效的專業化名稱;或者

    因為存在有效的專業化,所以不適用,特別是OnlyNumbers<C>是有效的,並且可以在類內部用於例如定義成員指針變量( ptr )。 實際上,通過刪除斷言並取消對ptr注釋, OnlyNumbers<Foo>行會編譯代碼。

  • [temp.res.8.2-8.4]不適用。

  • [temp.res.8.5]我也不認為這適用,但是我不能說我完全理解本節。

我的問題是:我的推理正確嗎? 僅在實例化模板的情況下,使用static_assert編譯特定[class] *模板的方法是否安全,符合標准?

*基本上,我對類模板感興趣,請隨時包含函數模板。 但是我認為規則是相同的。

**這意味着沒有T可以用於從外部實例化模板,就像可以從內部使用T=C一樣。 即使可以某種方式訪問C ,我也不認為有一種引用它的方法,因為它導致了這種遞歸OnlyNumbers<OnlyNumbers<...>::C>

編輯:

為了明確起見,我知道我可以構造一個表達式,如果其他任何一個專業都不匹配,則該表達式將完全為假。 但這很快就會變得冗長,並且如果專業變更,則容易出錯。

靜態斷言可以直接在類中使用,而無需做任何復雜的事情。

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    // ....
};

在某些情況下,您可能會收到其他錯誤消息,因為對於非算術類型實例化OnlyNumbers可能會導致更多編譯錯誤。

我不時使用的一個技巧是

#include <type_traits>

template<typename T>
struct OnlyNumbers {
    static_assert(std::is_arithmetic_v<T>, "T is not arithmetic type.");
    using TT = std::conditional_t<std::is_arithmetic_v<T>,T,int>;
    // ....
};

在這種情況下,您的類將使用有效類型int實例化。 由於靜態斷言無論如何都會失敗,因此不會產生負面影響。

好吧...我不明白你的意思

[[temp.res.8.1]]不存在,因為存在有效的專業化,尤其是OnlyNumbers有效,並且可以在類內部用於例如定義成員指針變量(ptr)。

您能否舉一個OnlyNumers有效和基於OnlyNumbers<C>編譯主模板的OnlyNumbers<C>

無論如何,在我看來,重點就在於此。

如果你問

僅在實例化模板的情況下,使用static_assert編譯特定[class] *模板的方法是否安全,符合標准?

在我看來(可能不包括僅在另一個專業匹配的情況下才是真的測試)由於[temp.res.8.1],答案為“否”。

也許您可以打開一扇打開的門來允許實例化,但只有有人(真的!)想要實例化它時才可用。

例如,您可以添加第三個模板參數,其默認值不同,如下所示

template<typename T, typename U = void, typename V = int>
struct OnlyNumbers
 {
   static_assert(std::is_same<T, U>::value, "test 1");
   static_assert(std::is_same<T, V>::value, "test 2");
 };

通過這種方式,您可以打開合法實例化的門

OnlyNumbers<Foo, Foo, Foo>     o1;
OnlyNumbers<void, void, void>  o2;
OnlyNumbers<int, int>          o3;

但僅說明至少第二種模板類型。

無論如何,為什么不簡單地避免定義模板的主版本呢?

// declared but (main version) not defined
template<typename T, typename = void>
struct OnlyNumbers;

// only specialization defined
template<typename T>
struct OnlyNumbers<T, std::enable_if_t<std::is_arithmetic_v<T>>>
 { };

由於無法實例化主模板,因此您的代碼格式錯誤。 請參閱Barry對您鏈接到的相關問題的答案中的標准報價。 您用來確保不能滿足明確規定的標准要求的回旋方式無濟於事。 停止對抗您的編譯器rsp。 標准,並采用Handy999的方法。 如果您仍然不想這樣做,例如出於DRY的原因,那么達到目標的一種一致方法將是:

template<typename T, typename Dummy = void>
struct OnlyNumbers{
public:
    struct C{};
    static_assert(! std::is_same<Dummy, void>::value, "T is not a number type.");

兩句話:

  • 首先,我故意替換了錯誤消息,因為錯誤消息“不是算術類型”會發出尖叫,必須測試! std::is_arithmetic<T>::value ! std::is_arithmetic<T>::value 如果您對“數字”類型有多個重載,其中有些可以滿足標准的算術類型要求,而另一些可能不滿足(例如,來自多精度庫的類型),那么我概述的方法可能有意義。
  • 其次,您可能反對有人可以編寫例如OnlyNumbers<std::string, int>來擊敗靜態斷言。 我要說的就是他們的問題。 請記住,每當您做出某種白痴證明時,自然就會使白痴變得更好。 ;-)說真的, 化妝的API,易於使用,而難以被濫用,但你不能修復的瘋狂,不應該打擾嘗試。

TL; DR:KISS和SWYM(說出您的意思)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM