libc++/libc++abi on Linux

LibreOffice bug report “Build failure with clang++/libc++ on Linux x86_64” and reports about a similar failure when trying to build on NetBSD made me look at building LibreOffice on Linux with Clang against libc++/libc++abi (instead of the usual libstdc++) myself.

The libc++ web site warns: “Unfortunately you can’t simply run clang with ‘-stdlib=libc++’ at this point [when you have built Clang to be able to use libc++ in combination with libc++abi], as clang is set up to link for libc++ linked to libsupc++.” But that appears to no longer be true (at least on current LLVM trunk). All it takes is to include the libcxx and libcxxabi repositories in the projects sub-directory of the LLVM/Clang source tree and build it. The resulting libc++.so includes the necessary -lc++abi, so building LibreOffice with CXX=clang++ -stdlib=libc++ just works. (You need to make sure that the libc++.so.1 gets found at runtime, of course, e.g., by setting LD_LIBRARY_PATH.)

One remaining problem is that libc++abi and libstdc++ (resp. its ABI-related libsupc++ part) differ in how they determine type equivalence. On the one hand, libc++abi sticks to the original C++ ABI semantics: “It is intended that two type_info pointers point to equivalent type descriptions if and only if the pointers are equal.” (That is, type equivalence is determined by comparing _ZTI* type_info pointers for equality.) On the other hand, libstdc++ relaxed this quite a while ago to comparing type_infos’ name fields (_ZTS*) for string equality (via strcmp).

While both approaches have pros and cons, they do lead to different behaviour. One example is when a dynamic library is dlopen’ed RTLD_LOCAL. If code in that library (or any library that gets loaded dependent of it) throws an exception of type T, and code beyond that RTLD_LOCAL “wall” shall catch it, then that will work with the libstdc++ approach but fail with the libc++abi approach (even if all the relevant _ZTI*, _ZTS* symbols are weakly exported).

That is the reason why e.g. the UNO runtime makes sure to dlopen all relevant libraries as RTLD_GLOBAL, but LibreOfficeKit/LibreOfficeKitInit.h somewhat ignorantly tries to get away with dlopen’ing the sofficeapp library as RTLD_LOCAL. Now LibreOffice’s cppunittester executable (used to run CppUnit tests during the LibreOffice build) links against the sal library. So by the time CppunitTest_libreofficekit_tiledrendering dlopen’s the sofficeapp library as RTLD_LOCAL, the sal library has already been loaded into the process (implicitly as RTLD_GLOBAL), so any UNO components that happen to get instantiated in that process (via the dlopen in sal’s osl_loadModule) will be outside that “sofficeapp RTLD_LOCAL walled garden,” so that when code in ucb/source/ucp/file/bc.cxx throws a css::ucb::CommandFailedException, code in ucbhelper/source/client/content.cxx (inside the walled garden) won’t catch it (but would instead proceed to std::unexpected, std::terminate, std::abort).

That problematic dlopen with RTLD_LOCAL had already been addressed for UBSan builds (where runtime type identification code in at least Clang’s UBSan implementation always operates under the original C++ ABI semantics, regardless of compiler ABI library used), “Ensure RTTI symbol visibility for Linux Clang -fsanitize=function,vptr.” The only problem this time is to find a suitable #if condition to distinguish usage of libc++abi from usage of other compiler ABI libraries, “[cfe-dev] Feature-test macro whether compiling against libc++abi?”

Advertisements

GCC 6

Some notes on building LibreOffice master (towards LO 5.2) with recent GCC trunk (towards GCC 6.0):

  • I ran into two open bugs with GCC that for now need workaround patches to be applied to LO:
  • (There was also another test breaker that took a while to track down as “Make sure desktop under LOK does not see osl_setCommandArgs CommandLineArgs.” But that one was not caused by any sort of mis-compilation, but rather by the fact that LO, when mistakenly asked to open a dynamic library as a document, happens to auto-detect an ELF library built by GCC 6 as a MacPaint document, while it auto-detected such libraries built with older GCC versions as plain text Writer documents.)
  • All the other (minor) fixes and tweaks have gone into the LO code base by now, and make check succeeded for me for both an (implicitly) --disable-debug as well as an --enable-dbgutil build (on some random Fedora 22 Linux box).
  • (With the caveat of having to --disable-firebird-sdbc in the --disable-debug case due to some error during building of that notorious external module,
    make -f ../gen/Makefile.refDatabases empty_db
    make[4]: Entering directory 'lo/core/workdir/UnpackedTarball/firebird/gen'
    make -f ../gen/Makefile.static.createdb
    make[5]: Entering directory 'lo/core/workdir/UnpackedTarball/firebird/gen'
    make[5]: Nothing to be done for 'all'.
    make[5]: Leaving directory 'lo/core/workdir/UnpackedTarball/firebird/gen'
    rm -f empty.fdb
    ../gen/firebird/bin/create_db empty.fdb
    invalid request BLR at offset 24
    -Too many Contexts of Relation/Procedure/Views. Maximum allowed is 255
    ../gen/Makefile.refDatabases:66: recipe for target 'empty.fdb' failed
    make[4]: *** [empty.fdb] Error 254
    make[4]: Leaving directory 'lo/core/workdir/UnpackedTarball/firebird/gen'
    Makefile:275: recipe for target 'empty_db' failed
    make[3]: *** [empty_db] Error 2
    make[3]: Leaving directory 'lo/core/workdir/UnpackedTarball/firebird/gen'
    Makefile:6: recipe for target 'firebird_embedded' failed
    make[2]: *** [firebird_embedded] Error 2
    make[2]: Leaving directory 'lo/core/workdir/UnpackedTarball/firebird'
    lo/core/external/firebird/ExternalProject_firebird.mk:29: recipe for target 'lo/core/workdir/ExternalProject/firebird/build' failed
    make[1]: *** [lo/core/workdir/ExternalProject/firebird/build] Error 1
    

    which I have not been able to track down yet.)

  • The most common new warning encountered was -Werror=misleading-indentation. We already have a Clang loplugin:bodynotinblock (“compiler plugin check for if/while/true bodies with possibly {} missing”), so this new GCC warning did not find any more errors, but it does warn about cases where an additional statement follows a complete if statement on the same line, like in “-Werror=misleading-indentation (GCC 6).”
  • The diagnostic information presented by the compiler when it finds an error in the code has improved still with GCC 6, but unfortunately the most dreaded
    lo/core/sw/source/core/text/txtfrm.cxx: In member function ‘virtual bool SwTextFrame::Prepare(PrepareHint, const void*, bool)’:
    lo/core/sw/source/core/text/txtfrm.cxx:689:0: error: assuming signed overflow does not occur when assuming that (X + c) < X is always false [-Werror=strict-overflow]
          if( nLen != COMPLETE_STRING && GetOfst() > nPos + nLen ) // the range preceded us
    

    emitted when building with optimizations enabled still leaves you completely clueless about what is going on, like in “Silence some odd -Werror=strict-overflow (GCC 6).”

  • (And PR66460 “ICE using __func__ in constexpr function” is still open, so we still have the somewhat unfortunate situation that we detect HAVE_CXX14_CONSTEXPR=1 in the --disable-debug case, but HAVE_CXX14_CONSTEXPR=0 in the --enable-debug case—or its --enable-dbgutil superset, or its --enable-assert-always-abort subset.)

(Update 2016-02-25: Current LibreOffice master builds fine now with recent GCC trunk rev. 233691 out of the box; no more workarounds needed on either side.)

Non-Obvious

Program 1:

#define min(a, b) ((a) < (b) ? (a) : (b))
struct S { static int const n = 0; };
int main() { return min(S::n, 1); }

Program 2:

#include <algorithm>
struct S { static int const n = 0; };
int main() { return std::min(S::n, 1); }

Program 3:

#include <algorithm>
struct S { static int const n = 0; };
int main() { return std::min({S::n, 1}); }

Where is the bug?

Right, program 2 lacks a definition of S::n.

std::min(T const &, T const &) takes its arguments by reference, so S::n does get odr-used there.

std::min(std::initializer_list<T>) does not have that gotcha.

LibreOffice in a Box

I had the pleasure of presenting my initial work on packaging LibreOffice as a sandboxed xdg-app bundle at GUADEC over the weekend.

Additionally, I dumped my experimental xdg-app repo for my LibreOffice build at http://people.redhat.com/sbergman/lo-repo now.  It is built off the current libreoffice-5-0-1 branch, with an autogen.input consisting of

--disable-odk
--disable-ooenv
--disable-systray
--enable-gtk3
--enable-release-build
--enable-werror
--disable-cups
--disable-gconf
--disable-gltf
--disable-gstreamer-1-0
--disable-orcus
--disable-postgresql_sdbc
--without-java
--with-system-libs
--without-system-altlinuxhyph
--without-system-bluez
--without-system-boost
--without-system-clucene
--without-system-coinmp
--without-system-cppunit
--without-system-curl
--without-system-firebird
--without-system-glew
--without-system-glm
--without-system-libabw
--without-system-libcdr
--without-system-libcmis
--without-system-libebook
--without-system-libetonyek
--without-system-libexttextcat
--without-system-libfreehand
--without-system-liblangtag
--without-system-libmspub
--without-system-libmwaw
--without-system-libodfgen
--without-system-libpagemaker
--without-system-librevenge
--without-system-libvisio
--without-system-libwpd
--without-system-libwpg
--without-system-libwps
--without-system-lpsolve
--without-system-mdds
--without-system-mythes
--without-system-neon
--without-system-npapi-headers
--without-system-odbc
--without-system-openldap
--without-system-poppler
--without-system-redland
--without-system-sane
--without-system-vigra

and some local patch

diff --git a/external/redland/ExternalProject_raptor.mk b/external/redland/ExternalProject_raptor.mk
index eaffcf1..dbc0649 100644
--- a/external/redland/ExternalProject_raptor.mk
+++ b/external/redland/ExternalProject_raptor.mk
@@ -48,7 +48,7 @@ $(call gb_ExternalProject_get_state_target,raptor,build):
 			$(if $(CROSS_COMPILING),--build=$(BUILD_PLATFORM) --host=$(HOST_PLATFORM)) \
 			$(if $(filter MACOSX,$(OS)),--prefix=/@.__________________________________________________OOO) \
 			$(if $(filter IOS ANDROID,$(OS)),--disable-shared,--disable-static) \
-			$(if $(SYSTEM_LIBXML),,--with-xml2-config=$(call gb_UnpackedTarball_get_dir,xml2)/xml2-config) \
+			$(if $(SYSTEM_LIBXML),--without-xml2-config,--with-xml2-config=$(call gb_UnpackedTarball_get_dir,xml2)/xml2-config) \
 		&& $(MAKE) \
 	)
 endif
diff --git a/fpicker/Library_fps_office.mk b/fpicker/Library_fps_office.mk
index c109dce..7ce952d 100644
--- a/fpicker/Library_fps_office.mk
+++ b/fpicker/Library_fps_office.mk
@@ -13,6 +13,7 @@ $(eval $(call gb_Library_Library,fps_office))
 $(eval $(call gb_Library_set_componentfile,fps_office,fpicker/source/office/fps_office))
 
 $(eval $(call gb_Library_use_external,fps_office,boost_headers))
+$(eval $(call gb_Library_use_external,fps_office,gio))
 
 $(eval $(call gb_Library_use_custom_headers,fps_office,\
 	officecfg/registry \
diff --git a/fpicker/source/office/OfficeFilePicker.cxx b/fpicker/source/office/OfficeFilePicker.cxx
index 6fd28ed..40698aa 100644
--- a/fpicker/source/office/OfficeFilePicker.cxx
+++ b/fpicker/source/office/OfficeFilePicker.cxx
@@ -43,6 +43,8 @@
 #include "osl/mutex.hxx"
 #include "vcl/svapp.hxx"
 
+#include <gio/gio.h>
+
 // using ----------------------------------------------------------------
 
 using namespace     ::com::sun::star::container;
@@ -504,29 +506,71 @@ void SAL_CALL SvtFilePicker::setTitle( const OUString& _rTitle ) throw (RuntimeE
 
 sal_Int16 SAL_CALL SvtFilePicker::execute(  ) throw (RuntimeException, std::exception)
 {
-    return OCommonPicker::execute();
-}
-
-
-// XAsynchronousExecutableDialog functions
-
-
-
-void SAL_CALL SvtFilePicker::setDialogTitle( const OUString& _rTitle ) throw (RuntimeException, std::exception)
-{
-    setTitle( _rTitle );
-}
-
-
-void SAL_CALL SvtFilePicker::startExecuteModal( const Reference< ::com::sun::star::ui::dialogs::XDialogClosedListener >& xListener )
-    throw (RuntimeException,
-           std::exception)
-{
-    m_xDlgClosedListener = xListener;
-    prepareDialog();
-    prepareExecute();
-    getDialog()->EnableAutocompletion( true );
-    getDialog()->StartExecuteModal( LINK( this, SvtFilePicker, DialogClosedHdl ) );
+    GError * err = nullptr;
+    GDBusProxy * proxy = g_dbus_proxy_new_for_bus_sync(
+        G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+        "org.freedesktop.portal.ContentPortal",
+        "/org/freedesktop/portal/content",
+        "org.freedesktop.portal.ContentPortal", nullptr, &err);
+    if (proxy == nullptr) {
+        assert(err != nullptr);
+        SAL_DEBUG("GError " << g_quark_to_string(err->domain) << " " << err->code << " " << err->message);
+        g_clear_error(&err);
+        std::abort();
+    }
+    assert(err == nullptr);
+    GVariant * ret = g_dbus_proxy_call_sync(
+        proxy, "Open", g_variant_new("(as)", nullptr), G_DBUS_CALL_FLAGS_NONE,
+        G_MAXINT, nullptr, &err);
+    bool cncl = false;
+    OUString id;
+    if (ret == nullptr) {
+        assert(err != nullptr);
+        cncl = g_error_matches(err, g_io_error_quark(), G_IO_ERROR_DBUS_ERROR);
+        if (!cncl) {
+            SAL_DEBUG("GError " << g_quark_to_string(err->domain) << " " << err->code << " " << err->message);
+            std::abort();
+        }
+        g_clear_error(&err);
+    } else {
+        //TODO type check
+        GVariant * v = g_variant_get_child_value(ret, 0);
+        gsize len;
+#if 0
+        gchar const * s = g_variant_get_string(v, &len);
+#else
+        gconstpointer p = g_variant_get_fixed_array(v, &len, 1);
+        char const * s = static_cast<char const *>(p);
+#endif
+        if (s == nullptr) {
+            SAL_DEBUG("nullptr");
+            std::abort();
+        }
+        if (!rtl_convertStringToUString(
+                &id.pData, s, len,
+                RTL_TEXTENCODING_UTF8,
+                (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
+                 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
+                 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
+        {
+            SAL_DEBUG("not UTF-8");
+            std::abort();
+        }
+        g_variant_unref(v);
+        g_variant_unref(ret);
+    }
+    g_object_unref(proxy);
+    SAL_DEBUG("CHOSE "<<int(!cncl)<<" <"<<id<<">");
+    if (cncl) {
+        return RET_CANCEL;
+    } else {
+#if 0
+        URL_ = "document:" + id;
+#else
+        URL_ = "file://" + id;
+#endif
+        return RET_OK;
+    }
 }
 
 
@@ -588,6 +632,10 @@ OUString SAL_CALL SvtFilePicker::getDisplayDirectory() throw( RuntimeException,
 
 Sequence< OUString > SAL_CALL SvtFilePicker::getFiles() throw( RuntimeException, std::exception )
 {
+if(true) {
+    SAL_DEBUG("getFiles <"<<URL_<<">");
+    return {URL_};
+}
     checkAlive();
 
     SolarMutexGuard aGuard;
diff --git a/fpicker/source/office/OfficeFilePicker.hxx b/fpicker/source/office/OfficeFilePicker.hxx
index fa8313e..685e40c 100644
--- a/fpicker/source/office/OfficeFilePicker.hxx
+++ b/fpicker/source/office/OfficeFilePicker.hxx
@@ -19,7 +19,7 @@
 #ifndef INCLUDED_FPICKER_SOURCE_OFFICE_OFFICEFILEPICKER_HXX
 #define INCLUDED_FPICKER_SOURCE_OFFICE_OFFICEFILEPICKER_HXX
 
-#include <cppuhelper/implbase5.hxx>
+#include <cppuhelper/implbase4.hxx>
 #include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
 #include <com/sun/star/ui/dialogs/XFilePreview.hpp>
 #include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
@@ -50,11 +50,10 @@ typedef ::com::sun::star::uno::Sequence< OUString >  OUStringList;   // can be t
 
 // class SvtFilePicker ---------------------------------------------------
 
-typedef ::cppu::ImplHelper5 <   ::com::sun::star::ui::dialogs::XFilePicker3
+typedef ::cppu::ImplHelper4 <   ::com::sun::star::ui::dialogs::XFilePicker3
                             ,   ::com::sun::star::ui::dialogs::XFilePickerControlAccess
                             ,   ::com::sun::star::ui::dialogs::XFilePreview
                             ,   ::com::sun::star::lang::XServiceInfo
-                            ,   ::com::sun::star::ui::dialogs::XAsynchronousExecutableDialog
                             >   SvtFilePicker_Base;
 
 class SvtFilePicker :public SvtFilePicker_Base
@@ -103,14 +102,6 @@ public:
     virtual sal_Int16 SAL_CALL execute(  ) throw (::com::sun::star::uno::RuntimeException, std::exception) SAL_OVERRIDE;
 
 
-    // XAsynchronousExecutableDialog functions
-
-    virtual void SAL_CALL setDialogTitle( const OUString& _rTitle ) throw (::com::sun::star::uno::RuntimeException, std::exception) SAL_OVERRIDE;
-    virtual void SAL_CALL startExecuteModal( const ::com::sun::star::uno::Reference< ::com::sun::star::ui::dialogs::XDialogClosedListener >& xListener )
-        throw (::com::sun::star::uno::RuntimeException,
-               std::exception) SAL_OVERRIDE;
-
-
     // XFilePicker functions
 
 
@@ -224,6 +215,8 @@ private:
     void                prepareExecute( );
 
     DECL_LINK(          DialogClosedHdl, Dialog* );
+
+    OUString URL_;
 };
 
 #endif // INCLUDED_FPICKER_SOURCE_OFFICE_OFFICEFILEPICKER_HXX

which on the one hand works around the xml-config problem I mention in the presentation and on the other hand hacks in some initial demo support for the document portal.

The latter works by replacing the LibreOffice-internal file picker (“Tools – Options… – LibreOffcie – General – Open/Save Dialogs – Use LibreOffice dialogs”) with the xdg-content-choser (you need the latest git revision of the chooser, along with the latest git revision of xdg-app for that to work). Once you select the LibreOffice-internal file picker, you can open a document from outside the sandbox, read-only for now. Read-write and saving back is next on the agenda…

ODR RTTI DSO

In C++, two types that have the same (fully qualified) name must be the same. The One Definition Rule (ODR) is there to ensure that, even if the types appear in different compilation units. C++ implementations are not required to diagnose all ODR violations, but they could be observed when features relying on runtime type information (exception handling, dynamic_cast, -fsanitize=vptr, …) start to misbehave.

What the C++ standard does not cover is how to pick unique names (by picking unique namespaces). That prevents composability. If you have two C++ libraries whose source code you cannot change, you cannot in general link them into the same program, as that might cause ODR violations.

What the C++ standard does not cover either is dynamic libraries and symbol visibility across such libraries. That lead C++ implementations to slightly bend the ODR rules in order to mitigate the above problem: If two different types that happen to have the same fully qualified name are hidden in two different dyanmic libraries, that is technically still an ODR violation, but no problems can arise from it. (In theory, it might also be possible for each library to place all its “internal” entities into unnamed namespaces, which would solve the same problems. In practice, however, a library’s developers will most likely want to spread it across multiple compilation units and still use such internal entities across compilation units, where unnamed namespaces would no longer work.)

A key aspect of the above is how to hide a type in a dynamic library. RTTI for a type is typically represented in C++ implementations as a set of symbols, one of them denoting a C-style string representation of the (mangled) type name. Then, if a C++ implementation uses comparison of string addresses (and not of string contents) to determine type equality, and if the RTTI string symbols are not coalesced across dynamic libraries at runtime (e.g., by keeping them non-exported), then hiding works.

And, on the other hand, an important corollary of the above is that if uses of a type across multiple dynamic objects shall be considered equal (so that e.g. an instance of that type can be thrown in one dynamic library and caught in another), the corresponding RTTI string symbols do need to be coalesced at runtime (e.g., by exporting them as weak symbols from all the dynamic objects using them).

That is why the Itanium C++ ABI mandates address comparison of RTTI strings. It is not only faster (to do the comparison, at least; though not necessarily to load the dynamic libraries and resolve the weak symbols for those types that shall be considered equal across dynamic libraries), it also enables composability.

However, a third thing the C++ standard does not cover is dynamic loading of (dynamic) libraries, with mechanisms like POSIX dlopen. An invocation of dlopen can be either RTLD_LOCAL or RTLD_GLOBAL. While RTLD_GLOBAL makes the exported symbols of the loaded library available to subsequently loaded libraries (which is important if there is any types that shall be considered equal across those libraries, see above), RTLD_LOCAL does not—and can thus break things like throwing exceptions across libraries.

The RTLD_LOCAL problem caused GCC maintainers to deviate from the Itanium C++ ABI and compare RTTI string symbols by content rather than by address. That implies breaking composability.

Clang still sticks to the Itanium C++ ABI’s by-address comparison, but that difference between GCC and Clang does not normally make a difference on Linux: Things like exception handling and dynamic_cast are handled by the C++ runtime library, and on Linux that is GCC’s libstdc++ regardless whether you compile wih GCC or Clang.

One case where it does make a difference is -fsanitize=function and -fsanitize=vptr, UBSan checks detecting certain undefined behavior involving function pointers or polymorphic object types, respectively. For Clang, the RTTI comparisons internally done by those checks are hard-coded in the compiler and would not call into libstdc++. So, when using -fsanitize=undefined:

On filling a vector

In C++11, what is the cheapest way to create a std::vector<T> with a bunch of statically known T elements (in terms of the number of special member functions of T invoked)?

In other words, which of the blocks in the below program produces the fewest lines of output:

#include <iostream>
#include <vector>
struct S {
    S() { std::cout << " default ctor\n"; }
    S(int) { std::cout << " int ctor\n"; }
    S(S const &) { std::cout << " copy ctor\n"; }
    S(S &&) { std::cout << " move ctor\n"; }
    ~S() { std::cout << " dtor\n"; }
    void operator =(S const &) { std::cout << " copy assignment\n"; }
    void operator =(S &&) { std::cout << " move assignment\n"; }
};
int main() {
    {
        std::cout << "A1:\n";
        std::vector<S> s { S(), S(), S() };
    }
    {
        std::cout << "A2:\n";
        std::vector<S> s { {}, {}, {} };
    }
    {
        std::cout << "A3:\n";
        std::vector<S> s { 0, 0, 0 };
    }
    {
        std::cout << "B1:\n";
        std::vector<S> s;
        s.reserve(3);
        s.push_back(S());
        s.push_back(S());
        s.push_back(S());
    }
    {
        std::cout << "B2:\n";
        std::vector<S> s;
        s.reserve(3);
        s.push_back({});
        s.push_back({});
        s.push_back({});
    }
    {
        std::cout << "B3:\n";
        std::vector<S> s;
        s.reserve(3);
        s.push_back(0);
        s.push_back(0);
        s.push_back(0);
    }
    {
        std::cout << "C1:\n";
        std::vector<S> s;
        s.reserve(3);
        s.emplace_back(S());
        s.emplace_back(S());
        s.emplace_back(S());
    }
    // {
    //  std::cout << "C2:\n";
    //  std::vector<S> s;
    //  s.reserve(3);
    //  s.emplace_back({});
    //  s.emplace_back({});
    //  s.emplace_back({});
    // }
    {
        std::cout << "C3:\n";
        std::vector<S> s;
        s.reserve(3);
        s.emplace_back(0);
        s.emplace_back(0);
        s.emplace_back(0);
    }
}

(Of course, each block needs at least three “dtor” lines when its s goes out of scope, so keep those out of the count.)

What might come as a surprise is that the A variants using an initializer list, though short and sweet, are rather heavy: Each one needs three constructors for the three elements of the initializer list (“default ctor” for A1 and A2, “int ctor” for A3), three copy constructors to copy the elements from the initializer list into the vector, and three destructors when destroying the initializer list. Nine calls to special member functions overall.

That is just as expensive as the B variants. (The difference being the order in which the special member functions are called for the individual elements.) However, the advantage of all three B variants is that they use the move constructor instead of the copy constructor to pass the temporary S instances into the vector.

(If you had hoped that the A variants would use the move constructors instead of the copy constructors, too: that does not work, as initializer lists only offer const access to their members.)

Variant C1 is not any different from variant B1. Still nine calls overall (employing the move constructor).

Variant C3, finally, is better: Although it looks more expensive to pass “wrong” int arguments that first need to be converted into proper S instances, the big benefit here is that those “int ctor” calls can directly instantiate the vector elements—no construction of temporary S instances, no copy or move constructors, no destruction of temporaries. Just three constructor calls overall.

(And variant C2 is commented out for good reason; there is just no way to tell emplace_back to instantiate an element directly into the vector through a default constructor call. But then again, most of the time what you want to put into the vector are not default-constructed elements, anyway.)

Win some, lose some

Seeing recent LibreOffice commit static const to avoid re-init all the time + c++11 got me thinking. (What the code does is check whether a given rtl::OUString instance is among a statically known sequence of strings.) Clearly we can do even better. (Even ignoring the question whether std::binary_search on a sorted sequence would do a better job here than std::find on an unordered one.)

For one, a std::initializer_list provides all the functionality requested here, and is more obviously reduced by compilers than a std::vector. (Except when the compiler has a bug, Properly check for Clang with static initializer_list bug.)

For another, a rtl::OUStringLiteral is a trivial wrapper around a string literal and its length. So if we allow for comparison between rtl::OUString and rtl::OUStringLiteral, and make the latter’s constructor constexpr (which is still not available with MSVC&nbps;2013, though), the sequence of strings can be fully built into the data section at compile time. Make OUStringLiteral more useful.

(A remaining question is whether it would be better here to use a variant of rtl::OUStringLiteral that operates on char16_t rather than ordinary string literals. The advantages would be using simpler comparison code and no requirement for the sequence of strings to be ASCII-only. The disadvantages would be a bigger data section and the sad fact that MSVC 2013, while knowing char16_t, still does not understand u"..." syntax.)

However, how much “better” is that, actually? Pointers in compile-time data (as are utilized by rtl::OUStringLiteral) require load-time fixups (in relocatable load-objects). So they would ideally be avoided.

One trick, at least for a sequence of strings of fairly equal length, would be to use a std::initializer_list of fixed-size buffers like

template<std::size_t M> struct FixedSizeLiteral {
    template<std::size_t N> constexpr FixedSizeLiteral(char const (&s)[N]):
        string_(s), length_(N - 1) {}
    char string_[M];
    std::size_t length_;
};

where M would need to be computed at compile time. But that would require turning the sequence of string literals into a template parameter pack, and string literals are not allowed as template arguments.

Another trick would be to replace the pointer stored in rtl::OUStringLiteral with an offset, but generating that data structure at compile time looks like it would also be beyond the capabilities of today’s C++.

As always, no silver bullet.