[llvm] [llvm] specialize cl::opt_storage for std::string (PR #149172)

Andrew Rogers via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 16 12:52:50 PDT 2025


https://github.com/andrurogerz created https://github.com/llvm/llvm-project/pull/149172

## Purpose
Specialize `llvm::cl::opt_storage` for data type `std::string` so that it no longer inherits from `std::string`. With this patch in place, explicitly instantiated `cl::opt<std::string>` can be safely exported from an LLVM Windows DLL.

## Overview
* Add fully-specified implementation of `cl::opt_storage` for data type `std::string`. It does NOT inherit from `std::string` as it did previously when data type `std::string` matched the partially specified template `cl::opt_storage<DataType, false, true>`
* Implement implicit conversions to `std::string`, `llvm::StringRef`, and `llvm::Twine` so that instances can be used in many places where `std::string` is used.
* Implement a number of `std::string` methods so that instances of the class are mostly interoperable with `std::string`.
* Add a new explicit constructor to `Triple` so it properly constructs from `cl::opt_storage<std::string>`. This change avoids having to modify clients that construct `Triple`s from `cl::opt<std::string>` instances.

## Background
This patch is in support of annotating LLVM's public symbols for Windows DLL export., tracked in #109483. Additional context is provided in [this discourse](https://discourse.llvm.org/t/psa-annotating-llvm-public-interface/85307).

This change is needed because, without it, we cannot export `opt<std::string>` from an LLVM Windows DLL. This is because MSVC exports all ancestor classes when exporting an instantiated template class. Since one of `opt`'s ancestor classes is its type argument (via `opt_storage`), it is an ancestor of `std::string`. Therefore, if we export `opt<std::string>` from the LLVM DLL, MSVC forces `std::basic_string` to also be exported. This leads to duplicate symbol errors and generally seems like a bad idea. Compiling with `clang-cl` does not exhibit this behavior.

## Validation
* Built llvm-project on Windows with MSVC `cl` and `clang-cl`.
* Built llvm-project on Fedora with `clang` and `gcc`.

>From 5748215d63720842980eaec4bb62a49190608fbf Mon Sep 17 00:00:00 2001
From: Andrew Rogers <andrurogerz at gmail.com>
Date: Wed, 16 Jul 2025 09:32:09 -0700
Subject: [PATCH] [llvm] specialize cl::opt_storage for std::string

---
 llvm/include/llvm/Support/CommandLine.h | 143 ++++++++++++++++++++++++
 llvm/include/llvm/TargetParser/Triple.h |   4 +
 2 files changed, 147 insertions(+)

diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h
index adaa75cc6c348..2e26980716f6c 100644
--- a/llvm/include/llvm/Support/CommandLine.h
+++ b/llvm/include/llvm/Support/CommandLine.h
@@ -1390,6 +1390,149 @@ class opt_storage<DataType, false, true> : public DataType {
   const OptionValue<DataType> &getDefault() const { return Default; }
 };
 
+// Define a fully-specialized version of opt_storage for std::string. This
+// implementation avoids inheriting from std::string while still being
+// compatible with std::string in common cases.
+//
+template <> class opt_storage<std::string, false, true> {
+  using Self = opt_storage<std::string, false, true>;
+
+public:
+  std::string Value;
+  OptionValue<std::string> Default;
+
+  // Make sure we initialize the value with the default constructor for the
+  // type.
+  opt_storage() : Value(std::string()), Default() {}
+
+  template <class T> void setValue(const T &V, bool initial = false) {
+    Value = static_cast<std::string>(V);
+    if (initial)
+      Default = static_cast<std::string>(V);
+  }
+
+  std::string &getValue() { return Value; }
+  std::string getValue() const { return Value; }
+
+  const OptionValue<std::string> &getDefault() const { return Default; }
+
+  // Support implicit conversions to std::string and LLVM string-like types.
+  operator std::string() const { return Value; }
+  operator llvm::StringRef() const { return llvm::StringRef(Value); }
+  operator llvm::Twine() const { return llvm::Twine(llvm::StringRef(Value)); }
+
+  // If the datatype is a pointer, support -> on it.
+  std::string operator->() const { return Value; }
+
+  template <typename T>
+  std::enable_if_t<std::is_convertible_v<T, std::string_view>, Self &>
+  operator=(const T &rhs) {
+    Value = std::string(rhs);
+    return *this;
+  }
+
+  template <typename T>
+  std::enable_if_t<std::is_convertible_v<T, std::string_view>, std::string &>
+  operator+=(const T &rhs) {
+    return Value += std::string_view(rhs);
+  }
+
+  template <typename T>
+  friend std::enable_if_t<std::is_convertible_v<T, std::string_view>,
+                          std::string>
+  operator+(const Self &lhs, const T &rhs) {
+    return (lhs.Value + std::string_view(rhs)).str();
+  }
+
+  template <typename T>
+  friend std::enable_if_t<std::is_convertible_v<T, std::string_view>,
+                          std::string>
+  operator+(const T &lhs, const Self &rhs) {
+    return (std::string_view(lhs) + rhs.Value).str();
+  }
+
+  template <typename T>
+  friend std::enable_if_t<std::is_convertible_v<T, std::string_view>, bool>
+  operator==(const Self &lhs, const T &rhs) {
+    return lhs.Value == std::string_view(rhs);
+  }
+
+  template <typename T>
+  friend std::enable_if_t<std::is_convertible_v<T, std::string_view>, bool>
+  operator==(const T &lhs, const Self &rhs) {
+    return std::string_view(lhs) == rhs.Value;
+  }
+
+  template <typename T>
+  friend std::enable_if_t<std::is_convertible_v<T, std::string_view>, bool>
+  operator!=(const Self &lhs, const T &rhs) {
+    return lhs.Value != std::string_view(rhs);
+  }
+
+  template <typename T>
+  friend std::enable_if_t<std::is_convertible_v<T, std::string_view>, bool>
+  operator!=(const T &lhs, const Self &rhs) {
+    return std::string_view(lhs) != rhs.Value;
+  }
+
+  friend raw_ostream &operator<<(raw_ostream &os, const Self &str) {
+    return os << str.Value;
+  }
+
+  // Implement a subset of std::string methods
+  size_t size() const noexcept { return Value.size(); };
+  size_t max_size() const noexcept { return Value.max_size(); };
+  size_t length() const noexcept { return Value.length(); };
+  bool empty() const noexcept { return Value.empty(); }
+  void clear() noexcept { Value.clear(); }
+
+  const char *c_str() const { return Value.c_str(); };
+  const char *data() const { return Value.data(); };
+
+  auto begin() noexcept { return Value.begin(); }
+  auto end() noexcept { return Value.end(); }
+  auto begin() const noexcept { return Value.begin(); }
+  auto end() const noexcept { return Value.end(); }
+  auto cbegin() const noexcept { return Value.cbegin(); }
+  auto cend() const noexcept { return Value.cend(); }
+
+  auto rbegin() noexcept { return Value.rbegin(); }
+  auto rend() noexcept { return Value.rend(); }
+  auto rbegin() const noexcept { return Value.rbegin(); }
+  auto rend() const noexcept { return Value.rend(); }
+  auto crbegin() const noexcept { return Value.rbegin(); }
+  auto crend() const noexcept { return Value.rend(); }
+
+  void erase(size_t pos = 0, size_t count = std::string::npos) {
+    Value.erase(pos, count);
+  }
+
+  size_t find(const std::string &str, size_t pos = 0) const {
+    return Value.find(str, pos);
+  }
+
+  size_t find(const char *s, size_t pos = 0) const {
+    return Value.find(s, pos);
+  }
+
+  size_t find(char c, size_t pos = 0) const { return Value.find(c, pos); }
+
+  std::string substr(size_t pos = 0, size_t count = std::string::npos) const {
+    return Value.substr(pos, count);
+  }
+
+  char operator[](size_t pos) const { return Value.at(pos); }
+
+  char& at(size_t pos) { return Value[pos]; }
+  const char& at(size_t pos) const { return Value.at(pos); }
+
+  char& front() { return Value.front(); }
+  const char& front() const { return Value.front(); }
+
+  char& back() { return Value.back(); }
+  const char& back() const { return Value.back(); }
+};
+
 // Define a partial specialization to handle things we cannot inherit from.  In
 // this case, we store an instance through containment, and overload operators
 // to get at the value.
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index 57d771b80251a..57d4c02d339c1 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -356,6 +356,10 @@ class Triple {
   explicit Triple(const std::string &Str) : Triple(std::string(Str)) {}
   LLVM_ABI explicit Triple(const Twine &Str);
 
+  template <typename S, typename = std::enable_if_t<
+                            std::is_convertible<S, StringRef>::value>>
+  explicit Triple(const S &Str) : Triple(StringRef(Str)) {}
+
   LLVM_ABI Triple(const Twine &ArchStr, const Twine &VendorStr,
                   const Twine &OSStr);
   LLVM_ABI Triple(const Twine &ArchStr, const Twine &VendorStr,



More information about the llvm-commits mailing list