1

假设我有以下模板函数:

// #include <iostream>

template< typename T >
T read( std::istream& in )
{
    T x;
    in >> x;  // (could check for failure, but not the point)
    return x;
}

它旨在像这样使用(const从用户输入初始化变量):

// #include <string>
// std::istream& in = std::cin;

const int integer = read< int >( in );
const double decimal = read< double >( in );
const std::string word = read< std::string >( in );
...

但请注意,必须提供两次相同的类型:一次用于变量声明,一次用于调用。最好避免这种重复(另请参阅DRY 原则,“不要重复自己”),因为每个更改都必须重复,例如这样的代码:

const int integer = read< double >( in );
const double decimal = read< int >( in );
const std::string word = read< char* >( in );

编译“很好”,但可能会在运行时做坏事(尤其是第三个,这会导致警告)。

有没有办法避免类型重复?


我已经可以想到两种方法,所以与其假装我不认识它们,我会在要求更多之前迅速暴露它们:

  1. 返回类型解析器

    我最近发现“返回类型解析器”成语(来自这个问题)。所以我们可以围绕现有函数创建一个带有模板隐式转换运算符的简单类:

    class Read {
        std::istream& m_in;
    public:
        explicit Read( std::istream& in ) : m_in( in ) { }
    
        template< typename T >
        operator T() const
        {
            return read< T >( m_in );
        }
    };
    

    我们现在可以编写如下代码:

    const int integer = Read( in );
    const double decimal = Read( in );
    const std::string word = Read( in );
    ...
    

    其中“返回类型”是从变量类型自动推导出来的(注意:即使变量是,返回类型也不const是,所以这确实等同于原始代码(尤其是在可能的内联之后)。

  2. C++11auto

    C++11开始,我们可以使用说明auto作为替代,这也避免了两次命名类型,但方法是“逆向的”:

    const auto integer = read< int >( in );
    const auto decimal = read< double >( in );
    const auto word = read< std::string >( in );
    ...
    

    其中变量类型是从返回类型自动推导出来的。

还要别的吗?

现在,您知道其他选择吗?[如果您想知道,请参阅我对以下评论的回复,了解此问题的“原因”。]

谢谢你。

4

2 回答 2

1

这是一种为每个返回类型推导函数将样板打包在一个位置的方法:

// usual sequence boilerplate:
template<unsigned... s> struct seq { typedef seq<s...> type; };
template<unsigned max, unsigned... s > struct make_seq:make_seq<max-1, max-1, s...> {};
template<unsigned... s> struct make_seq<0, s...>:seq<s...> {};

// RTR object, which wraps a functor F to do return type deduction:
template<template<typename>class F>
struct RTR {
  // Stores a `tuple` of arguments, which it forwards to F when cast to anything:
  template<typename... Args>
  struct worker {
    std::tuple<Args...> args;
    worker ( Args&&... a ):args(std::forward<Args>(a)...) {}
    template<typename T, unsigned... s>
    T call(seq<s...>) const {
      return F<T>()( std::forward<Args>(std::get<s>(args))... );
    }
    template<typename T>
    operator T() const
    {
      return call<T>(make_seq<sizeof...(Args)>());
    }
  };
  // Here operator() creates a worker to hold the args and do the actual call to F:
  template<typename... Args>
  worker<Args...> operator()( Args&&... args ) const {
    return {std::forward<Args>(args)...};
  }
};

// We cannot pass function templates around, so instead we require stateless functors:
template<typename T>
struct read {
  T operator()( std::istream& in ) const {
    T x;
    in >> x;
    return x;
  }
};

// and here we introduce reader, a return type deducing wrapper around read:
namespace { RTR<read> reader; }

您必须为上述样板编写大量返回类型推导代码,使其少于每个特定用途的样板。

于 2013-07-24T12:53:48.023 回答
1

好吧,您仍然可以使用预处理器,即使您可能出于文体原因不喜欢它。你可以像这样使用它:

#define READ(type, variable, stream) type variable = read<type>(stream)

在此之后,您可以将作业简单地写为

READ(int, integer, in);

当然,这将变量的定义及其初始化隐藏在类似函数的构造后面,我不喜欢,但这是一个可能的解决方案。

于 2013-07-24T16:11:26.983 回答