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):
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?