【问题标题】:Don't use sizeof for T if T is a function如果 T 是一个函数,不要对 T 使用 sizeof
【发布时间】:2017-05-20 08:14:19
【问题描述】:

我有近乎以下的结构来检测类型是否可以按值传递:

template <class T>
struct should_be_passed_by_value {
    static constexpr bool value = 
        std::is_scalar<T>::value || 
        std::is_array<T>::value || 
        std::is_reference<T>::value || 
        (sizeof(T) <= sizeof(void*));
};

问题是:当我为类 C 函数指针或 std::function 实例化它时,编译器会说:

invalid application of 'sizeof' to a function type

(当然)。

如何修改它以使value 包含false?

【问题讨论】:

  • 您使用的是哪个编译器?你能举一个产生错误信息的例子吗?我尝试了 'should_be_passed_by_value::value' 并使用 Visual Studio 2015 和 gcc 4.9 成功编译。
  • @FrançoisAndrieux 试试void foo() {} 然后should_be_passed_by_value&lt;decltype(foo)&gt;::value;
  • @OP 你能显示产生错误的调用代码吗?
  • 你确定它不适用于 std::function 吗??它当然应该!也许您应该提供一个最小的、完整的示例,更多地说明您是如何做到这一点的。
  • @AndyG,我猜作者真的想将大小与 ptr 大小进行比较,而不是检查它是否为 void* ?

标签: c++ templates c++14 function-pointers


【解决方案1】:

如何修改它以使 value 包含 false?

任何问题都可以通过额外的间接层来解决。我们已经内置了其中一些。基本上,您希望仅在 T 不是函数时使用您的小型检查。已经有一个元函数:std::conditional。我们可以使用它来延迟评估。

小的检查,我们分离出它自己的元函数:

template <class T>
struct is_small
    : std::integral_constant<bool, (sizeof(T) <= sizeof(void*))>
{ };

然后我们可以将你的条件改写为:

template <class T>
struct should_be_passed_by_value {
    static constexpr bool value = 
        std::is_scalar<T>::value || 
        std::is_array<T>::value || 
        std::is_reference<T>::value || 
        std::conditional_t<
            std::is_function<T>::value,
            std::false_type,
            is_small<T>>::value;
};

这样,is_small&lt;T&gt; 只有在 T 不是函数时才会被实例化。

【讨论】:

    【解决方案2】:

    如何修改它以使值包含 false?

    它遵循should_be_passed_by_value 的可能实现(实际上是一个最小的工作示例):

    #include<type_traits>
    #include<functional>
    
    template <class T, typename = void>
    struct should_be_passed_by_value: std::false_type {};
    
    template <class T>
    struct should_be_passed_by_value
    <T, std::enable_if_t<
        (std::is_scalar<T>::value ||
        std::is_array<T>::value ||
        std::is_reference<T>::value || 
        (sizeof(T) <= sizeof(void*)))
    >>: std::true_type {};
    
    void f() {}
    
    int main() {
        static_assert(should_be_passed_by_value<int>::value, "!");
        static_assert(should_be_passed_by_value<char>::value, "!");
        static_assert(not should_be_passed_by_value<std::function<void(void)>>::value, "!");
        static_assert(not should_be_passed_by_value<void(void)>::value, "!");
        static_assert(should_be_passed_by_value<void(*)(void)>::value, "!");
    }
    

    基本思想是依靠部分专业化。
    此外,您实际上不必定义自己的value 数据成员。因为你使用的是C++14,所以should_be_passed_by_value可以直接继承std::false_type和std::true_type。

    默认情况下,您的类型 T 不应按值传递(should_be_passed_by_value 继承自 std::false_type)。
    如果T 没有通过所有检查,则由于std::enable_if_t 的工作方式,专业化将被丢弃。因此选择了主模板,这意味着 T 不应按值传递。
    如果T 通过所有检查,则std::enable_if_t 是void,并且专业化优先于主模板。请注意,特化继承自 std::true_type,这意味着在这种情况下,T 应按值传递。

    从示例中可以看出,std::function、函数类型和所有其他内容都可以轻松透明地处理,不会对原始表达式进行任何添加。

    【讨论】:

      【解决方案3】:

      我无法完全按照您的描述重现该问题,但如果我正确理解该问题,您可以使用template specialization 彻底解决此问题。以下示例使用 Visual Studio 2015 和 gcc 4.9 进行编译。

      #include <type_traits>
      
      // Non-function types
      template <class T>
      struct should_be_passed_by_value 
      {
          static constexpr bool value = 
              std::is_scalar<T>::value || 
              std::is_array<T>::value || 
              std::is_reference<T>::value || 
              (sizeof(T) <= sizeof(void*));
      };
      
      // Function type
      template <class Return, class ... Args>
      struct should_be_passed_by_value<Return(Args...)> 
      {
          static constexpr bool value = false; // What value for functions?
      };
      

      这里有一些编译用例

      // All of these use cases compile
      #include <array>
      const auto u = should_be_passed_by_value<std::array<int, 10>>::value;
      const auto v = should_be_passed_by_value<int*()>::value;
      const auto w = should_be_passed_by_value<int()>::value;
      const auto x = should_be_passed_by_value<int(int)>::value;
      const auto y = should_be_passed_by_value<int*>::value;
      const auto z = should_be_passed_by_value<int>::value;
      

      【讨论】:

      • 这是模板专业化,不是 sfinae。
      • 可变参数函数需要单独的特化:should_be_passed_by_value&lt;int(...)&gt;::value。形式上,C 语言链接函数也需要单独的特化,但普通编译器不实现这一点。实际上,非标准调用约定也需要单独的特化,但没有标准的方法来指定它。检测(使用 SFINAE)未能使用 sizeof 可能会更好,在这种情况下,一次涵盖所有选项。
      • ... 它需要const、const &amp;、const &amp;&amp; 和一堆其他专业。那已经不是很干净了。
      【解决方案4】:

      部分答案(正如它在 cmets 中提到的,不清楚什么对 std::function 不起作用,它应该是)

      您可以结合enable_if_t 和is_function 将类型空间分为两部分,函数和“其余部分”:

      #include <type_traits>
      #include <functional>
      #include <iostream>
      
      template <class T, class Enable = void>
      struct should_be_passed_by_value; // primary case that we will never hit
      
      template <class T>
      struct should_be_passed_by_value<T, typename std::enable_if_t<std::is_function<T>::value>>
      {
        static constexpr bool value = false; // case 0
      };
      
      template <class T>
      struct should_be_passed_by_value<T, typename std::enable_if_t<!std::is_function<T>::value>>
      {
        static constexpr bool value =
            std::is_scalar<T>::value || // case 1
            std::is_array<T>::value || // case 2
            std::is_reference<T>::value || // case 3
            (sizeof(T) <= sizeof(void *)); /// case 4
      };
      
      void testF(){};
      
      int main()
      {
        std::function<void()> f;
        std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<decltype(testF)>::value << std::endl; // result 0, case 0
      
        std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<decltype(5)>::value << std::endl; // res 1, case 1
      
        std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<char[42]>::value << std::endl; // res 1, case 2
      
        std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<int&>::value << std::endl; // res 1 , case 3
      
        struct Small {char _{2};};
        std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<Small>::value << std::endl; // res 1, case 4
      
        struct Big {char _[16];};
        std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<Big>::value << std::endl; // res 0, case 4
      
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-02-25
        • 2016-12-12
        • 2018-03-09
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        • 2010-10-22
        相关资源
        最近更新 更多