Using a C++ function that takes a string from Swift from inside a swift package

The following C++ function in the C++ part of the Library

std::string echo_this(std::string yodel) {
    return yodel;
}

The following Swift function in the Swift part of the library

    public func sendAndReceiveString(send:String) -> String {
        let received = echo_this(send)
        return String(received)
    }

The return value gets casted fine. Yay! The value to send doesn't. That's a heavy lift.

 error: cannot convert value of type 'String' to expected argument type 'std.__1.string' (aka 'std.__1.basic_string<Int8, char_traits<Int8>, allocator<Int8>>')

But neither do echo_this(send.utf8) or echo_this(send.utf8CString) or echo_this(send.utf16) or my syntax guess at echo_this(std::string(send)), but I might be missing an import at the top of the Swift to get the C++ std types?

I feel like this was solved but I can't quite find the right sauce.

I'd be open to is using the swift::String type mentioned in the WWDC videos as the parameter type for the C++. I'm not clear on what I'd need to add to the header files for that. I suspect that's the actually the harder route not the easier.

Thanks in advance.

I don't really know, but I suspect you need to be explicit when converting a Swift string to a C++ std::string. Did you try:

let received = echo_this(std.string(send))
3 Likes

And THAT's the syntax. Thank you. So std.type(swift similar type) is the casting syntax? Perfect.

So embarrassed - I had not yet made it far enough down the page: Swift.org - Mixing Swift and C++

FWIW: I don't seem to need the

import CxxStdlib

if Foundation is already imported, but it will suffice if Foundation is removed.

Importing Foundation shouldn't have an effect on this. The reason you don't need an explicit import CxxStdlib is that you're importing a C++ library that includes the C++ stdlib headers, e.g. <string>. This makes all of the C++ stdlib APIs visible to Swift without the need for an explicit import.

To be a bit more specific, the initialization syntax is the same as for pure Swift types: SwiftTypeName(someValue). The std. prefix comes from the fact that string is declared in namespace std on the C++ side. A C++ type MyType declared in namespace NS will be available in Swift as NS.MyType.

You are correct, I did have a library imported, but I was so impressed that the casting worked for me without a special import I wanted to push it. I removed my import Foundation only (not my import cxxLibrary) and the line with std.string complained that it didn't know what it was anymore. I added back in just CxxStdlib and it stopped complaining.

I actually then took out the import CxxStdlib and added #import <Foundation/Foundation.h> to the C++ Library's umbrella header and that also seemed to silence the warning (that's a pass through, yes?), so I assumed it was Foundation doing something.

I commented out my library, and with just Foundation imported tossed in an let x = std.string("hello") and XCode accepted it just fine. This matched for me how easy it is to toss C into Swift, so I just colored myself amazed and didn't question it.

I didn't change any other imports during this process.

This project is a Package. I'm working on an Intel Mac with XCode Version 15.0 beta 6 (15A5219j). I didn't compile, though. This was just what XCode was or was not happy about. I don't know what VSCode was saying at that time I didn't check, but I can recreate/try compiling if that'd be useful.

FWIW its the same demo project from my other question:

UPDATED: For the record, double checked how the project works in its current state. Recognizing std.string() in ThingCatalog.swift does NOT get turned off when just commenting out import Foundation or just the C++ library import anymore. They both can enable that on their own. I think its important to clarify that its undisputed that a C++ Library with just #include <string> in one of the included header files and not also <Foundation/Foundation.h> in the umbrella also enables std.string() to work. My previous comments with THIS code instead of the tested code I think could cause that confusion. Also updating link to be a frozen branch instead of one I'm still tinkering with.

:+1: . That makes total sense.

1 Like

So I began to wonder if it was the presence of #include <string> in the project AT ALL anywhere that was letting ThingCatalog recognize std.string with just a Foundation import.

So I made a branch that now has NO C++ dedicated product or target at all and JUST Swift products & targets, but with each of them having swiftSettings: [.interoperabilityMode(.Cxx)]

Still just import Foundation enables std.string() to compile.

Removing swiftSettings: [.interoperabilityMode(.Cxx)] from the package targets turns it off. Trying to get it back by JUST putting in import CxxStdlib gets the message cannot load underlying module for 'CxxStdlib'. That all seems logical, in a Package it being tied to the swiftSetting.

So I made a quick XCode project (Combined Platform App), updated the interop build setting to C++/ObjectiveC and again... Magic.

import SwiftUI //<-- brings Foundation with it

struct ContentView: View {
    let test = std.string("hello")
    var body: some View {
        VStack {
            Image(systemName: String(test))
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}
import Foundation

struct Things {
    let test = std.string("hello")
}

Sounds like this interaction with Foundation is unexpected or new but it certainly will make interacting with C++ code incredibly easy.

Wow. Nice.