【问题标题】:SFINAE to make base template always result in errorSFINAE 使基础模板总是导致错误
【发布时间】:2016-08-31 13:08:22
【问题描述】:

所以我正在设计一种my_numeric_cast 函数来限制使用我正在编写的框架时可用的转换类型。

做这样的事情很简单

template<typename To, typename From>
constexpr To my_numeric_cast(From);

template<>
constexpr float my_numeric_cast<float, int>(int i) { return i; }

这很有效,只要使用强制转换,就只允许从整数转换为浮点数。每当尝试进行不在白名单中的强制转换时,就会产生链接错误。

但是,我真的很想让它成为编译错误,以便更快地发现误用。
如何使基本模板主体有效,在实例化时期望?

【问题讨论】:

  • 为什么要使用模板?您可以添加重载,如果它们调用不存在的重载,则会出现编译器错误。
  • @NathanOliver,您不能重载函数的返回类型。这往往会导致歧义。

标签: c++ template-meta-programming sfinae


【解决方案1】:

您不能编写没有模板参数使主体在 C++ 中有效的模板函数特化。如果这样做,结果是一个格式错误的程序,不需要诊断。这包括主要专业化。

所以这里的大多数答案只是未定义的行为。它们可能有效,但它们不是有效的 C++。它们今天可能工作,但是在库升级、编译器升级或不同的构建目标之后,它们可能会以完全不同和令人惊讶的方式失败。没有充分理由依赖 UB 是个坏主意。

从好的方面来说,我们可以取消模板专业化并一举解决您的问题:

template<class T>struct tag_t{}; // may need `constexpr tag_t(){}` on some compilers
template<class T>constexpr tag_t<T> tag{};

template<class T, class F>
constexpr T my_numeric_cast(F, tag_t<F>)=delete; // generates compile time error

constexpr float my_numeric_cast(int i, tag_t<float>) { return i; } // not a template!  Could be if you want it to be.

template<typename To, typename From>
constexpr To my_numeric_cast(From f){
  return my_numeric_cast(f, tag<To>);
}

完成了。

=delete 生成友好消息。程序结构良好。实施演员表不再是一门专业。您甚至可以在启用 ADL 时在类型的命名空间中实现它。

如果您解决了模板函数专业化的问题,请重新考虑。它们很脆弱,不像类模板特化或函数重载那样工作(虽然看起来像它们!),并且通常不是解决任何问题的最佳解决方案。当它可能是一个好主意时也有例外,但它们非常罕见,考虑到它们是多么罕见地避免这个古怪的功能可能仍然值得。

【讨论】:

  • 这很有启发性。我做了专业化,因为它是我学习时的教科书示例。谢谢!
  • +1 因为我喜欢使用= delete。只是我很好奇,我的回答是不是你提到的错误?我很确定它是健壮的,可能有点复杂,比你的差得多,但它不是基于 UB。我错了吗?
  • @skypjack 不。您的模板专业化至少对一种类型有效。据我所知,该规则的存在允许(未来的迭代)C++ 语言(不进行重大更改)或编译器本身在模板参数替换和早期出错之前进行额外的检查以验证模板代码。任何会使代码对 all 参数无效的事情都是公平的。
  • @Yakk 确实很有趣。感谢您每次提供更多详细信息,非常感谢。
  • @template [temp.res/8] 然后进入一组扭曲的标准段落。请注意术语专业化和模板,它们并不完全意味着它们在标准 iirc 之外的含义。我试图搜索关于它的 SO Q&A,在 5 分钟内没有找到它(但确实找到了那个临时资源参考)
【解决方案2】:

您可以使用特征来获取编译时错误:

template<typename, typename> struct RetTraits;
// Enable it for int -> float
template<> struct RetTraits<int, float> { using type = float; };

template<typename To, typename From>
using RetType = typename RetTraits<To, From>::type;

template<typename To, typename From>
constexpr RetType<To, From> my_numeric_cast(From f) {
    return To(f);
}

int main() {
    my_numeric_cast<int, float>(42);
    // This won't compile
    // my_numeric_cast<int, int>(42);
}

【讨论】:

    【解决方案3】:

    这是一个更新的答案,可以在 c++11 之前的代码中使用。我仍然觉得 Yakk 的回答非常棒,以及他关于函数模板专业化的建议,但我觉得我应该发布这个以确保完整性。

    如果理解正确,此解决方案不会导致程序格式错误,甚至会给出相当清晰的错误消息。 它利用使用类模板的概念将主模板标记为不完整,但仍使用函数模板特化。

    namespace detail {
        template<typename T1, typename T2> struct InvalidInstantiation;
    }
    
    template<typename To, typename From>
    To my_cast (From f) {
        detail::InvalidInstantiation<To, From> No_Function_Exists;
    }
    
    template<>
    float my_cast(int i) { return i; }
    
    int main() {
        my_cast<float>(7);
        //my_cast<int>(7.0); // This will result in an error
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-18
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多