如何在C++中打印std::vector<std::any>向量的元素?

6
系统能够使用 a.type().name() 区分每个元素的类型,但是无法打印它们?
#include <iostream>
#include <vector>
#include <any>
#include <string>
#include <algorithm>
    
template<typename Last>
void addElement(std::vector<std::any>& container, Last last) {
    std::cout << "Last = " << last << std::endl;
    container.push_back(last);
}

template<typename First, typename... Rest>
void addElement(std::vector<std::any>& container, First first, Rest... rest) {
    std::cout << "Elem = " << first << std::endl;
    container.push_back(first);
    addElement(container, rest...);
}

template<typename... Ts>
std::vector<std::any> createAnyVector(Ts... ts) {
    std::vector<std::any> container;
    addElement(container, ts...);
    return container;
}

int main() {
    std::cout << "ANYVECTOR" << std::endl;

    std::vector<std::any> container = createAnyVector("Hello", 3.14, 'A', true, 42);

    std::cout << "Number of elements in container = " << container.size() << std::endl; // 5 correct.

    for (const auto& a : container) {

        std::cout << a.type().name() << ", " << "HERE?" << std::endl;
    }
}

如果我只在当前位置的HERE?处写入a,那么它会返回错误:
No operator << matches these operands
operand types are: std::basic_ostream<char, std::char_traits<char>> << const << std::any

1
你不能打印它,因为如果它是无法打印的类型呢? - user253751
1
错误提示您无法为打印std::any定义operator<<,这是正确的。 std::any :: type()返回运行时分配给std::any的任何类型的RTTI的std::type_info&,但是operator<<必须在编译时知道。 您是否希望std::any定义一个operator<<来处理可能分配给它的每种可能类型? 这是不可能的。 - Remy Lebeau
你需要设计一个操作符或其他机制来在运行时选择程序中可能的适当数据类型,像 std::variant 这样的标签式联合会更容易实现。就像 Remy 说的那样。 - Swift - Friday Pie
1
不仅你无法打印它们,而且你几乎无法对它们做任何事情。所有类型的常见接口是什么?没有。使用 std::variant - Quimby
1
从您的个人资料来看,我猜测您不太深入研究底层的东西。如果您觉得这种限制难以置信,那么自己实现std::any可能会让您茅塞顿开,了解为什么会出现这种情况,以及为获得此功能所需付出的代价。此外,在C++中,std::any几乎总是错误的。 - Passer By
3个回答

6
正如评论所指出的,您不能直接操作 std::any,只能保持它们。
如果您控制API并有选项在std::any周围创建包装器,则可以维护一个函数指针以打印当前std::any的内容,如下所示:
struct printable_any {
  template <class T>
  printable_any(T&& t) : m_val(std::forward<T>(t)) {
    m_print_fn = [](std::ostream& os, const std::any& val) {
      os << std::any_cast<std::decay_t<T>>(val);
    };
  }

private:
  using print_fn_t = void(*)(std::ostream&, const std::any&);
  std::any m_val;
  print_fn_t m_print_fn;
  
  friend std::ostream& operator<<(std::ostream& os, const printable_any& val) {
    val.m_print_fn(os, val.m_val);
    return os;
  }
};

请注意,即使使用此方法,您也不能随意打印一个随机的std::any,您必须自己构造它并附上函数指针。
用法:
int main() {
    std::vector<printable_any> vals {
        "Hello", 3.14, 'A', true, 42
    };

    for (auto& v : vals) {
        std::cout << v << '\n';
    }
}

打印

Hello
3.14
A
1
42

https://godbolt.org/z/o1sejrv1e


2

好的,这将会打印什么:

struct Foo {
  void *p;
};

any v = Foo{};
cout << v << '\n'; // what should this print?

C++中没有类似于toString的方法。如果你想要这种行为,你可以创建自己的继承体系:

struct Printable {
    virtual void print(ostream&) const = 0;
};

auto& operator<<(ostream& os, any const& obj) {
    any_cast<Printable const&>(obj).print(os);
    return os;
}

或者您可以根据自己的需要进行自定义,并按照自己的喜好处理错误情况,如果需要,还可以为整数类型添加特殊情况等。


std::to_string是什么? - 0dminnimda
@0dminnimda std::to_string 只适用于9种内置类型。它不是将所有内容转换为字符串的通用方法。 - Ayxan Haqverdili

0

向量必须是任意类型吗?还是允许至少指定存储在向量中的类型?因为如果您可以至少指定允许的类型,我建议使用std::variant

例如:std::vector<variant<int, string>> myVectorVariant;

之后,您可以通过使用std::variant中的std::get_if将向量中的项目实际打印到控制台。下面是可复制的函数,用于输出大多数主要原始类型的向量:int、float、char、string。

#include <variant>
#include <vector>
#include <iostream>
using namespace std;

void printVectorVariantValues(vector<variant<int, float, char, string>> arg) {
  try {
      for (auto val : arg) {
          if (const auto intPtr (get_if<int>(&val)); intPtr) 
              cout << *intPtr << " ";
          else if (const auto floatPtr (get_if<float>(&val)); floatPtr)
              cout << *floatPtr << " ";
          else if (const auto charPtr (get_if<char>(&val)); charPtr)
              cout << *charPtr <<  " ";
          else if (const auto stringPtr (get_if<string>(&val)); stringPtr) 
              cout << *stringPtr << " ";
      }    
  } catch (bad_variant_access err) {
      cout << "something" << " ";
  }
  
  cout << "DONE" << endl;
}

int main() {
    vector<variant<int, float, char, string>> myVectorVariant = {1, 2, 'a', 'b', 3, 0.4f, 0.5f, "c", "DeF", 0.6f, "gHi", "JkL" };
    printVectorVariantValues(myVectorVariant);
    return 0;
}

/* 
output below: 
1 2 a b 3 0.4 0.5 c DeF 0.6 gHi JkL DONE
*/

虽然正确,但这并没有回答如何打印 std::any 对象的问题。话说,对于 std::variant 的打印,使用 std::visit 会更容易,因为您不需要知道任何类型,也不需要有 n 个 if 分支。基本上只需 std::visit([](const auto& v){ std::cout << v << " "; }, val); 即可。 - Human-Compiler
@Human-Compiler - 是的,我同意我的答案并没有直接回答问题,但提供了解决该问题的另一种方法。正如您可能会同意原始评论下的OP问题所述,根本问题是除非您进行包装器实现,否则无法打印std :: any类型。因此,我建议采用不同的方法,这可能效果更好。 - Brian Wang
@Human-Compiler - 另外,我也同意你的第二个说法,即std::visit比我的建议更简单实用,但首先,我之所以这样详细地写出来是为了教育目的,以防OP不理解std::variant本身。其次,在实际调试产业级代码时,虽然不需要知道所有类型有助于代码简洁,但std::visit实际上会带来技术债务,因为调试该问题的开发人员不知道他们可能或可能不控制的API需要什么类型。 - Brian Wang
1
std::visit 实际上存在技术债务……”--需要引用证明。调试器会告诉你正在操作的类型,这里没有什么疑问。即使没有运行时类型信息,获取类型名称甚至用于日志记录也不难,而且由于是 std::variant,开发人员非常清楚他们正在操作的类型。您暗示像 std::visit 这样的常数时间解决方案存在某种形式的技术债务,而不是重复查询 RTTI 的 else-if 阶梯,这有点令人担忧。我模糊地理解了“出于教育目的”的说法,但不理解该语句的其他部分。 - Human-Compiler
你刚才自相矛盾了。“你建议某种形式的技术债务有点令人担忧”,但是“为了记录目的,获取类型名称并不难”。你明确指出需要更多的实现才能获得你的类型,却仍然没有意识到你的陈述所暗示的问题,这让我感到担忧。我只是提供了另一种选择,如果我的else if能帮助任何开发人员更快地了解他们的类型,那就更好了。 - Brian Wang
是的,调试器可以告诉你正在操作的类型,但在代码正常工作且仍在查找问题源头的情况下无法解决。最后,使用 variant,开发人员知道他们正在处理哪些类型,但不知道正在处理的数据在那个时刻;这仍然需要查询。 - Brian Wang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接