【问题标题】:Friend template function instantiations that match parameter与参数匹配的朋友模板函数实例化
【发布时间】:2019-11-29 23:12:20
【问题描述】:

我有一个模板类,它应该有一个朋友:一个make_object 函数,它允许扣除某些类型和值。我希望只与那些与模板类的类型匹配的实例成为朋友。我的代码的简化版本如下:

template<size_t S, typename T>
class Object {
private:
    Object(T t);

    template<typename U, typename... Args>
    friend auto make_object(U, Args... args);
};


template<typename U, typename... Args>
inline auto make_object(U u, Args... args)
{
    constexpr size_t S = sizeof...(Args);
    return std::unique_ptr<Object<S, U>>(new Object<S, U>(u));
}

以这段代码为例,我希望只与make_object 的那些typename U 匹配对象的typename T 的实例交朋友。这可能吗?

【问题讨论】:

    标签: c++ templates instantiation friend


    【解决方案1】:

    如果您的要求只是与具有与T 相同模板参数的函数模板成为朋友,那么这就足够了:

    #include <cstdint>
    
    template <typename T, typename ... TArgs>
    auto make_object(T, TArgs...);
    
    template<std::size_t size, typename T>
    class Object {
    private:
        Object(T t);
        friend auto make_object<T>(T);
    };
    
    template <typename T, typename ... TArgs>
    auto make_object(T t, TArgs... args) {
        Object<0u, T>{t};   // Compiles
    }
    
    template <>
    auto make_object<float>(float f) {
        // Error: Constructor is private
        Object<0u, int>{static_cast<int>(f)};  
    }
    

    Compiler Explorer

    【讨论】:

      【解决方案2】:

      您可以使用类将make_objectU 参数(必须与ObjectT 匹配)与Args 参数包分开,后者不必匹配。然后,与 helper 类交朋友,让你的函数的所有实例都可以访问 Objectprivate 成员。

      template<typename U>
      struct make_object_t;
      
      template<std::size_t S, typename T>
      class Object {
      private:
          Object(T t);
      
          friend class make_object_t<T>;
      };
      
      template<typename U>
      struct make_object_t {
          template<typename... Args>
          static auto make_object(U u, Args... args) {
              constexpr std::size_t S = sizeof...(Args);
              return std::unique_ptr<Object<S, U>>(new Object<S, U>(u));
          }
      };
      

      最后,编写一个辅助函数来隐藏 API 中的类:

      template<typename U, typename... Args>
      auto make_object(U&& u, Args&&... args) {
          return make_object_t<U>::template make_object<Args...>(std::forward<U>(u), std::forward<Args>(args)...);
      }
      

      现在,例如这行得通

      int main() {
          std::unique_ptr<Object<3, int>> ptr = make_object(5, 'a', 0x0B, "c");
      }
      

      但是,例如make_object_t&lt;char&gt;不能使用Object&lt;S, int&gt;::Object

      template<>
      struct make_object_t<char> {
          template<typename... Args>
          static auto make_object(char u, Args... args) {
              constexpr std::size_t S = sizeof...(Args);
              return std::unique_ptr<Object<S, int>>(new Object<S, int>(u));
          }
      };
      int main() {
          // error here from instantiation of above function template
          auto oops = make_object('n', 'o');
      }
      

      【讨论】:

        【解决方案3】:

        如果您想拥有一个朋友模板,您可以将模板的所有实例都设为朋友,就像在您的示例中一样,或者您可以将完整的专业化设为朋友。

        不幸的是,两者之间没有任何关系。

        要解决这个问题,您可以将 make_object 函数包装在模板类中,并使该模板类成为朋友。

        #include <type_traits>
        #include <iostream>
        #include <memory>
        
        template<typename U>
        struct object_maker;
        
        template<std::size_t S, typename T>
        class Object {
        private:
            Object(T) {}
        
            friend struct object_maker<T>;
        };
        
        
        template<typename U>
        struct object_maker
        {
            template<typename... Args>
            static auto make_object(U u, Args... args)
            {
                constexpr std::size_t S = sizeof...(Args);
                return std::unique_ptr<Object<S, U>>(new Object<S, U>(u));
            }
        };
        
        int main()
        {
            auto obj = object_maker<int>::make_object(7);
            static_assert(std::is_same_v<decltype(obj), std::unique_ptr<Object<0,int>>>);
        }
        

        【讨论】:

        • 我认为这是我的答案?
        • 时间机器对我们的社会是一个可怕的威胁:)
        【解决方案4】:

        据我了解,make_object() 是一个便利函数模板,它利用模板参数推导来创建Object 对象。

        您可以创建一个额外的函数模板,例如create_object(),它对应于与Object 类模板相同的模板参数:size_t 和一个类型模板参数。然后,您可以轻松地将Object 中的友谊授予此函数模板的实例,该实例与Object 具有相同的模板参数:

        template<size_t, typename>
        class Object;
        
        template<size_t S, typename T>
        inline auto create_object(T t) {
           return std::unique_ptr<Object<S, T>>(new Object<S, T>(t));
        }
        
        template<size_t S, typename T>
        class Object {
        private:
            Object(T t);
            friend auto create_object<S, T>(T);
        };
        

        您原来的make_object() 函数模板只是委托给这个新的函数模板create_object()

        template<typename U, typename... Args>
        inline auto make_object(U u, Args... args)
        {
            constexpr size_t S = sizeof...(Args);
            return create_object<S, U>(std::move(u));
        }
        

        一旦您知道参数包Args 中的元素数量,您就不再需要参数包了。您只需将推导的U 和计算的S 作为模板参数传递给create_object()

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-18
          相关资源
          最近更新 更多