0

I'm trying to develop some C++ skills by doing a light open-source project using Visual Studio 2019.

The solution includes two projects: one for the application (luacalls), one for the tests (luacalls-tests). Instead of declaring everything as __cdecl/DLLEXPORT like some answers here recommend, I decided simply to include all source files from the application project into the test project as well (as the application is expected to be quite small in terms of LoC). Here's what it looks like in Solution Explorer (pardon the Russian):

Solution Explorer

Here's main.cpp from luacalls-tests:

#include <sstream>

#include "gtest/gtest.h"
#include "../luacalls/run.h"
#include "../luacalls/lexer.h"

TEST(Run, RunThrowsOnInvalidFilename) {
    char* argv[]{ "_", "Z:\\nonexistent.file" };
    ASSERT_THROW(run(2, argv), std::runtime_error);
}

TEST(LexerTests, LexerParsesIntegers) {
    std::stringstream ss{ "9519292" };
    Lexer lexer{ std::move(ss) };
    auto lexer_iter = lexer.begin();
    EXPECT_EQ(std::get<double>(*lexer_iter), 9519292);
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

The run function is called without issue and passes the corresponding test. The other test, however, fails to compile with 3 linking errors LNK2019 ("Unresolved external symbol"). run and Lexer both have their declaration and definition split into .h and .cpp files respectively. Here's lexer.h:

#pragma once
#include <variant>

enum class Keyword {
    // something...
};

enum class Operator {
    // something else...
};

enum class SyntaxToken {
    // something else again...
};

using Identifier = std::string;
using StringLiteral = std::string;
using Token = std::variant<Keyword, Identifier, StringLiteral, long long, double, Operator, SyntaxToken>;

template <typename InputStream>
struct Lexer {
    struct LexerIteratorEnd {
        bool operator!=(LexerIteratorEnd);
    };

    struct LexerIterator {
        friend Lexer;
        LexerIterator(LexerIterator&);
        Token& operator*();
        LexerIterator& operator++();
        LexerIterator operator++(int);
        bool operator!=(LexerIteratorEnd) const;
    private:
        LexerIterator(Lexer<InputStream>);
        Lexer<InputStream>& lexer_;
    };

    Lexer(InputStream&&);
    LexerIterator begin();
    LexerIteratorEnd end();
    bool empty();
private:
    void advance();
    InputStream is_;
    Token current_lexeme_{};
};

And lexer.cpp as well:

#include <variant>
#include <string>
#include <istream>
#include <fstream>

#include "lexer.h"

template <typename InputStream>
bool Lexer<InputStream>::LexerIteratorEnd::operator!=(LexerIteratorEnd ignored) {
    return false;
}

// LexerIterator

template <typename InputStream>
Lexer<InputStream>::LexerIterator::LexerIterator(LexerIterator& other) : lexer_{ other.lexer_ } {}

template <typename InputStream>
typename Lexer<InputStream>::LexerIterator& Lexer<InputStream>::LexerIterator::operator++() { // prefix increment
    lexer_.next();
}

template <typename InputStream>
typename Lexer<InputStream>::LexerIterator Lexer<InputStream>::LexerIterator::operator++(int) { // postfix increment
    LexerIterator tmp{ *this };
    operator++();
    return tmp;
}

template <typename InputStream>
bool Lexer<InputStream>::LexerIterator::operator!=(LexerIteratorEnd ignored) const {
    LexerIterator tmp{ *this };
    operator++();
    return tmp;
}

template <typename InputStream>
Token& Lexer<InputStream>::LexerIterator::operator*() {
    return lexer_.current_lexeme_;
}

template <typename InputStream>
Lexer<InputStream>::LexerIterator::LexerIterator(Lexer<InputStream> lexer) : lexer_{ lexer } {}

// Lexer

template <typename InputStream>
Lexer<InputStream>::Lexer(InputStream&& is) : is_{ std::move(is) } {}

template <typename InputStream>
typename Lexer<InputStream>::LexerIterator Lexer<InputStream>::begin() {
    return LexerIterator{ *this };
}

template <typename InputStream>
typename Lexer<InputStream>::LexerIteratorEnd Lexer<InputStream>::end() {
    return LexerIterator{ *this };
}

template <typename InputStream>
bool Lexer<InputStream>::empty() {
    return is_.eof();
}

template <typename InputStream>
void Lexer<InputStream>::advance() {
    current_lexeme_ = 25.0;
}

Quick Internet search reveals that "Unresolved external symbol" error occurs when there's a declaration without the corresponding definition. However, I'm pretty sure I've defined every method for the class (and Visual Studio helpfully highlights declarations for which it can't find definitions). Any pointers would be appreciated. Maybe I should man up and switch to some "canonical" way of coupling testing facilities with the application?

Atmaks
  • 391
  • 3
  • 14
  • 1
    I think your bug is you implemented your template in a .cpp file. – drescherjm Jun 09 '20 at 13:17
  • Wow. How is one even supposed to know about that? BTW, the code above has a billion errors, as it turns out. Don't use it, please ;) And thank you for the answer, of course! – Atmaks Jun 09 '20 at 15:10

0 Answers0